cta_redux 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +22 -0
  6. data/README.md +16 -0
  7. data/Rakefile +2 -0
  8. data/cta_redux.gemspec +32 -0
  9. data/data/.gitkeep +0 -0
  10. data/data/cta-gtfs.db.gz +0 -0
  11. data/lib/cta_redux/api/api_response.rb +45 -0
  12. data/lib/cta_redux/api/bus_tracker.rb +178 -0
  13. data/lib/cta_redux/api/customer_alerts.rb +68 -0
  14. data/lib/cta_redux/api/train_tracker.rb +89 -0
  15. data/lib/cta_redux/bus_tracker.rb +183 -0
  16. data/lib/cta_redux/customer_alerts.rb +72 -0
  17. data/lib/cta_redux/faraday_middleware/bus_tracker_parser.rb +46 -0
  18. data/lib/cta_redux/faraday_middleware/customer_alerts_parser.rb +39 -0
  19. data/lib/cta_redux/faraday_middleware/simple_cache.rb +32 -0
  20. data/lib/cta_redux/faraday_middleware/train_tracker_parser.rb +37 -0
  21. data/lib/cta_redux/models/agency.rb +4 -0
  22. data/lib/cta_redux/models/bus.rb +46 -0
  23. data/lib/cta_redux/models/calendar.rb +7 -0
  24. data/lib/cta_redux/models/route.rb +62 -0
  25. data/lib/cta_redux/models/shape.rb +4 -0
  26. data/lib/cta_redux/models/stop.rb +66 -0
  27. data/lib/cta_redux/models/stop_time.rb +6 -0
  28. data/lib/cta_redux/models/train.rb +74 -0
  29. data/lib/cta_redux/models/transfer.rb +6 -0
  30. data/lib/cta_redux/models/trip.rb +64 -0
  31. data/lib/cta_redux/train_tracker.rb +103 -0
  32. data/lib/cta_redux/version.rb +3 -0
  33. data/lib/cta_redux.rb +50 -0
  34. data/script/gtfs_to_sqlite.rb +137 -0
  35. data/spec/bus_tracker_spec.rb +149 -0
  36. data/spec/customer_alerts_spec.rb +48 -0
  37. data/spec/spec_helper.rb +16 -0
  38. data/spec/stubs/alerts_response.xml +362 -0
  39. data/spec/stubs/getdirections_response.xml +16 -0
  40. data/spec/stubs/getpatterns_rt22_response.xml +2768 -0
  41. data/spec/stubs/getpredictions_rt22stpid15895_response.xml +59 -0
  42. data/spec/stubs/getpredictions_vid4361_response.xml +647 -0
  43. data/spec/stubs/getroutes_response.xml +774 -0
  44. data/spec/stubs/getservicebulletins_rt8_response.xml +110 -0
  45. data/spec/stubs/getstops_response.xml +614 -0
  46. data/spec/stubs/gettime_response.xml +2 -0
  47. data/spec/stubs/getvehicles_rt22_response.xml +239 -0
  48. data/spec/stubs/getvehicles_vid4394_response.xml +23 -0
  49. data/spec/stubs/route_status8_response.xml +1 -0
  50. data/spec/stubs/routes_response.xml +1 -0
  51. data/spec/stubs/ttarivals_stpid30141_response.xml +1 -0
  52. data/spec/stubs/ttfollow_run217_response.xml +1 -0
  53. data/spec/stubs/ttpositions_response.xml +1 -0
  54. data/spec/train_tracker_spec.rb +70 -0
  55. metadata +234 -0
@@ -0,0 +1,6 @@
1
+ module CTA
2
+ class Transfer < Sequel::Model
3
+ many_to_one :from_stop, :class => 'CTA::Stop', :key => :from_stop_id
4
+ many_to_one :to_stop, :class => 'CTA::Stop', :key => :to_stop_id
5
+ end
6
+ end
@@ -0,0 +1,64 @@
1
+ module CTA
2
+ class Trip < Sequel::Model
3
+ L_ROUTES = ["Brn", "G", "Pink", "P", "Org", "Red", "Blue", "Y"]
4
+ BUS_ROUTES = CTA::Trip.exclude(:route_id => L_ROUTES).select_map(:route_id).uniq
5
+ plugin :single_table_inheritance,
6
+ :route_id,
7
+ :model_map => proc { |v|
8
+ if L_ROUTES.include?(v)
9
+ 'CTA::Train'
10
+ else
11
+ 'CTA::Bus'
12
+ end
13
+ },
14
+ :key_map => proc { |klass|
15
+ if klass.name == 'CTA::Train'
16
+ L_ROUTES
17
+ else
18
+ BUS_ROUTES
19
+ end
20
+ }
21
+
22
+ set_primary_key :trip_id
23
+
24
+ many_to_one :calendar, :key => :service_id
25
+ one_to_many :stop_times, :key => :trip_id
26
+
27
+ many_to_one :route, :key => :route_id
28
+
29
+ many_to_many :stops, :left_key => :trip_id, :right_key => :stop_id, :join_table => :stop_times
30
+
31
+ # DRAGONS
32
+ # The CTA doesn't exactly honor the GTFS spec (nor do they return GTFS trip_ids
33
+ # in the API, grr). They specify multiple entries of # (schd_trip_id, block_id, service_id)
34
+ # so the only way to know which trip_id to pick is to join against stop_times and
35
+ # calendar dates, and # find out which run (according to stop_times) is happening *right now*.
36
+ # Of course, this will break if the train is delayed more the total time
37
+ # it takes to complete the run... so a delayed train will start to disappear
38
+ # as it progresses through the run. We allow for a 'fuzz factor' to account
39
+ # for this...
40
+ def self.find_active_run(run, timestamp, fuzz = false)
41
+ if self.to_s == "CTA::Train" # This is admittedly hacky.
42
+ join_str = "WHERE t.schd_trip_id = 'R#{run}'"
43
+ else
44
+ join_str = "WHERE t.route_id = '#{run}'"
45
+ end
46
+ d = timestamp.is_a?(DateTime) ? timestamp : DateTime.parse(timestamp)
47
+ wday = d.strftime("%A").downcase
48
+ end_ts = (fuzz ? (d.to_time + (60 * 60 * 6) - (90 * 60)) : d).strftime("%H:%M:%S")
49
+ Trip.with_sql(<<-SQL)
50
+ SELECT t.*
51
+ FROM trips t
52
+ JOIN stop_times st ON t.trip_id = st.trip_id
53
+ JOIN calendar c ON t.service_id = c.service_id
54
+ #{join_str}
55
+ AND c.start_date <= '#{d.to_s}'
56
+ AND c.end_date >= '#{d.to_s}'
57
+ AND c.#{wday}
58
+ GROUP BY t.trip_id, st.departure_time
59
+ HAVING MAX(st.departure_time) >= '#{end_ts}'
60
+ SQL
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,103 @@
1
+ module CTA
2
+ class TrainTracker
3
+ def self.connection
4
+ raise "You need to set a developer key first. Try CTA::TrainTracker.key = 'foo'." unless @key
5
+
6
+ @connection ||= Faraday.new do |faraday|
7
+ faraday.url_prefix = 'http://lapi.transitchicago.com/api/1.0/'
8
+ faraday.params = { :key => @key }
9
+
10
+ faraday.use CTA::TrainTracker::Parser, !!@debug
11
+ faraday.response :caching, SimpleCache.new(Hash.new)
12
+ faraday.adapter Faraday.default_adapter
13
+ end
14
+ end
15
+
16
+ def self.arrivals!(options={})
17
+ allowed_keys = [:route, :parent_station, :station, :limit]
18
+ if options.keys.any? { |k| !allowed_keys.include?(k) }
19
+ raise "Illegal option!"
20
+ end
21
+
22
+ has_map = options.has_key?(:parent_station)
23
+ has_stop = options.has_key?(:station)
24
+
25
+ route = Array.wrap(options[:route]).flatten.compact.uniq
26
+ map = Array.wrap(options[:parent_station]).flatten.compact.uniq
27
+ stop = Array.wrap(options[:station]).flatten.compact.uniq
28
+ limit = Array.wrap(options[:limit]).flatten.compact.uniq.first.to_i if options[:limit]
29
+
30
+ if route.size > 1
31
+ raise "No more than 1 route may be specified!"
32
+ end
33
+
34
+ if map.size > 1 || stop.size > 1
35
+ raise "No more than 1 station or parent_station may be specified!"
36
+ end
37
+
38
+ if !(has_map || has_stop)
39
+ raise "You must specify a station or a parent_station! Try arrivals(:station => 30280..."
40
+ end
41
+
42
+ params = {}
43
+ params.merge!({ :mapid => map.first }) if options[:parent_station]
44
+ params.merge!({ :stpid => stop.first }) if options[:station]
45
+ params.merge!({ :max => limit }) if options[:limit]
46
+ params.merge!({ :rt => route.first }) if route.any?
47
+
48
+ connection.get('ttarrivals.aspx', params)
49
+ end
50
+
51
+ def self.predictions!(options={})
52
+ self.arrivals!(options)
53
+ end
54
+
55
+ def self.follow!(options={})
56
+ raise "Must specify a run! Try follow(:run => 914)..." unless options.has_key?(:run)
57
+
58
+ runs = Array.wrap(options[:run]).flatten.compact.uniq
59
+
60
+ if runs.size > 1
61
+ raise "Only one run may be specified!"
62
+ end
63
+
64
+ connection.get('ttfollow.aspx', { :runnumber => runs.first })
65
+ end
66
+
67
+ def self.locations!(options={})
68
+ unless options.has_key?(:routes)
69
+ raise "Must specify at least one route! (Try locations(:routes => [:red, :blue]) )"
70
+ end
71
+
72
+ rt = Array.wrap(options[:routes]).flatten.compact.map { |r| (CTA::Train::FRIENDLY_L_ROUTES[r] || r).to_s }
73
+
74
+ if rt.size > 8
75
+ raise "No more than 8 routes may be specified!"
76
+ end
77
+
78
+ connection.get('ttpositions.aspx', { :rt => rt.join(',') })
79
+ end
80
+
81
+ def self.positions!(options={})
82
+ self.locations!(options)
83
+ end
84
+
85
+ def self.key
86
+ @key
87
+ end
88
+
89
+ def self.key=(key)
90
+ @key = key
91
+ @connection = nil
92
+ end
93
+
94
+ def self.debug
95
+ !!@debug
96
+ end
97
+
98
+ def self.debug=(debug)
99
+ @debug = debug
100
+ @connection = nil
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,3 @@
1
+ module CTA
2
+ VERSION = "0.1.0"
3
+ end
data/lib/cta_redux.rb ADDED
@@ -0,0 +1,50 @@
1
+ require "cta_redux/version"
2
+ require "sequel"
3
+ require "sqlite3"
4
+ require "faraday"
5
+ require "faraday_middleware"
6
+ require "multi_xml"
7
+ require "zlib"
8
+
9
+ module CTA
10
+ base_path = File.expand_path("..", __FILE__)
11
+ data_dir = File.join(File.expand_path("../..", __FILE__), 'data')
12
+ Dir.glob("#{base_path}/cta_redux/faraday_middleware/*") { |lib| require lib }
13
+ Dir.glob("#{base_path}/cta_redux/api/*") { |lib| require lib }
14
+ require "#{base_path}/cta_redux/train_tracker.rb"
15
+ require "#{base_path}/cta_redux/bus_tracker.rb"
16
+ require "#{base_path}/cta_redux/customer_alerts.rb"
17
+
18
+ db_filename = File.join(data_dir, 'cta-gtfs.db')
19
+
20
+ # First run
21
+ if !File.exists?(db_filename)
22
+ dbf = File.open(db_filename, 'wb')
23
+ Zlib::GzipReader.open("#{db_filename}.gz") do |gz|
24
+ dbf.puts gz.read
25
+ end
26
+ dbf.close
27
+ end
28
+
29
+ DB = Sequel.sqlite(:database => db_filename, :readonly => true)
30
+
31
+ Dir.glob("#{base_path}/cta_redux/models/*") do |lib|
32
+ next if lib =~ /train/ || lib =~ /bus/
33
+ require lib
34
+ end
35
+
36
+ require "#{base_path}/cta_redux/models/train.rb"
37
+ require "#{base_path}/cta_redux/models/bus.rb"
38
+ end
39
+
40
+ class Array
41
+ def self.wrap(object)
42
+ if object.nil?
43
+ []
44
+ elsif object.respond_to?(:to_ary)
45
+ object.to_ary || [object]
46
+ else
47
+ [object]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sqlite3'
4
+ require 'csv'
5
+ require 'date'
6
+
7
+ KNOWN_COLUMN_TYPES = {
8
+ "calendar" => {
9
+ "service_id" => "INTEGER",
10
+ "monday" => "BOOLEAN",
11
+ "tuesday" => "BOOLEAN",
12
+ "wednesday" => "BOOLEAN",
13
+ "thursday" => "BOOLEAN",
14
+ "friday" => "BOOLEAN",
15
+ "saturday" => "BOOLEAN",
16
+ "sunday" => "BOOLEAN",
17
+ "start_date" => "DATE",
18
+ "end_date" => "DATE"
19
+ },
20
+ "routes" => {
21
+ "route_type" => "INTEGER"
22
+ },
23
+ "shapes" => {
24
+ "shape_id" => "INTEGER",
25
+ "shape_pt_lat" => "FLOAT",
26
+ "shape_pt_lon" => "FLOAT",
27
+ "shape_pt_sequence" => "INTEGER",
28
+ "shape_dist_traveled" => "INTEGER"
29
+ },
30
+ "stop_times" => {
31
+ "trip_id" => "INTEGER",
32
+ # This would be nice to store as a native SQL type,
33
+ # but ruby really has no notion of a time object w/o
34
+ # an associated date, because ruby stores time as epoch.
35
+ # so, in order to prevent ruby from attaching dates to our
36
+ # stop_times, we just let it store naturally as a string.
37
+ #"arrival_time" => "TIME",
38
+ #"departure_time" => "TIME",
39
+ "stop_id" => "INTEGER",
40
+ "stop_sequence" => "INTEGER",
41
+ "shape_dist_traveled" => "INTEGER",
42
+ "pickup_type" => "INTEGER"
43
+ },
44
+ "stops" => {
45
+ "stop_id" => "INTEGER",
46
+ "stop_code" => "INTEGER",
47
+ "stop_lat" => "FLOAT",
48
+ "stop_lon" => "FLOAT",
49
+ "location_type" => "INTEGER",
50
+ "parent_station" => "INTEGER",
51
+ "wheelchair_boarding" => "BOOLEAN"
52
+ },
53
+ "transfers" => {
54
+ "from_stop_id" => "INTEGER",
55
+ "to_stop_id" => "INTEGER",
56
+ "transfer_type" => "INTEGER"
57
+ },
58
+ "trips" => {
59
+ "service_id" => "INTEGER",
60
+ "trip_id" => "INTEGER",
61
+ "direction_id" => "INTEGER",
62
+ "block_id" => "INTEGER",
63
+ "shape_id" => "INTEGER",
64
+ "wheelchair_accessible" => "BOOLEAN"
65
+ }
66
+ }
67
+
68
+ def usage
69
+ "Usage: gtfs_to_sqlite.rb input_file output_db"
70
+ end
71
+
72
+ abort usage unless ARGV.size == 2
73
+
74
+ base_path = File.expand_path("..", __FILE__)
75
+ input_file = File.join(base_path, ARGV[0])
76
+ output_db = File.join(base_path, ARGV[1])
77
+
78
+ table = input_file.split(File::SEPARATOR).last.gsub(".txt", "")
79
+
80
+ puts input_file
81
+ abort usage unless File.exists?(input_file)
82
+
83
+ db = SQLite3::Database.new(output_db)
84
+
85
+ first_row = true
86
+ n = 0
87
+ headers = nil
88
+
89
+ print "Loading"
90
+ CSV.foreach(input_file, :headers => true) do |row|
91
+ if n % 100 == 0
92
+ print "."
93
+ end
94
+ if n % 1000 == 0
95
+ print n
96
+ end
97
+
98
+ if first_row
99
+ columns = row.headers.map do |r|
100
+ if KNOWN_COLUMN_TYPES[table] && KNOWN_COLUMN_TYPES[table][r]
101
+ col = "#{r} #{KNOWN_COLUMN_TYPES[table][r]}"
102
+ else
103
+ col = r
104
+ end
105
+ end
106
+ stmt = "CREATE TABLE #{table} (#{columns.join(',')})"
107
+ db.execute(stmt)
108
+ first_row = false
109
+ headers = row.headers
110
+ end
111
+
112
+ stmt = "INSERT INTO #{table} (#{row.headers.join(',')}) VALUES (#{row.headers.map { '?' }.join(',')})"
113
+ if row.headers.any? { |h| h =~ /date/ }
114
+ fields = []
115
+ row.headers.each_with_index do |header, index|
116
+ if header =~ /date/
117
+ fields << DateTime.parse(row.fields[index]).to_s
118
+ else
119
+ fields << row.fields[index]
120
+ end
121
+ end
122
+ else
123
+ fields = row.fields
124
+ end
125
+ db.execute(stmt, fields)
126
+ n +=1
127
+ end
128
+
129
+ puts "\ndone"
130
+ puts "Creating indices..."
131
+ if headers
132
+ headers.each do |h|
133
+ if h =~ /_id/
134
+ db.execute("CREATE INDEX #{table}_#{h}_index ON #{table} (#{h})")
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,149 @@
1
+ base_path = File.expand_path(File.dirname(__FILE__))
2
+ data_path = File.expand_path(File.join(base_path, 'stubs'))
3
+
4
+ require File.join(base_path, "..", "lib", "cta_redux")
5
+
6
+ RSpec.describe CTA::BusTracker do
7
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
8
+ stub.get('/bustime/api/v2/gettime?key=') { |env| [200, {}, File.read(File.join(data_path, 'gettime_response.xml'))] }
9
+ stub.get('/bustime/api/v2/getvehicles?key=&rt=22') { |env| [200, {}, File.read(File.join(data_path, 'getvehicles_rt22_response.xml'))] }
10
+ stub.get('/bustime/api/v2/getvehicles?key=&vid=4394') { |env| [200, {}, File.read(File.join(data_path, 'getvehicles_vid4394_response.xml'))] }
11
+ stub.get('/bustime/api/v2/getroutes?key=') { |env| [200, {}, File.read(File.join(data_path, 'getroutes_response.xml'))] }
12
+ stub.get('/bustime/api/v2/getdirections?key=&rt=22') { |env| [200, {}, File.read(File.join(data_path, 'getdirections_response.xml'))] }
13
+ stub.get('/bustime/api/v2/getstops?key=&rt=22&dir=Northbound') { |env| [200, {}, File.read(File.join(data_path, 'getstops_response.xml'))] }
14
+ stub.get('/bustime/api/v2/getpatterns?key=&rt=22') { |env| [200, {}, File.read(File.join(data_path, 'getpatterns_rt22_response.xml'))] }
15
+ stub.get('/bustime/api/v2/getpredictions?key=&rt=22&stpid=15895&top=&vid=') { |env| [200, {}, File.read(File.join(data_path, 'getpredictions_rt22stpid15895_response.xml'))] }
16
+ stub.get('/bustime/api/v2/getpredictions?key=&vid=4361') { |env| [200, {}, File.read(File.join(data_path, 'getpredictions_vid4361_response.xml'))] }
17
+ stub.get('/bustime/api/v2/getservicebulletins?key=&rt=8') { |env| [200, {}, File.read(File.join(data_path, 'getservicebulletins_rt8_response.xml'))] }
18
+ end
19
+
20
+ CTA::BusTracker.key = ''
21
+ CTA::BusTracker.connection.instance_variable_get(:@builder).delete(Faraday::Adapter::NetHttp)
22
+ CTA::BusTracker.connection.adapter :test, stubs
23
+
24
+ describe "time!" do
25
+ result = CTA::BusTracker.time!
26
+
27
+ it "returns an error code" do
28
+ expect(result.error).to be_instance_of(CTA::API::Error)
29
+ expect(result.error.code).to eq(0)
30
+ expect(result.error.message).to eq("OK")
31
+ end
32
+
33
+ it "returns a time" do
34
+ expect(result.timestamp).to be_instance_of(DateTime)
35
+ expect(result.timestamp.to_s).to eq("2015-02-14T11:31:13+00:00")
36
+ end
37
+ end
38
+
39
+ describe "vehicles!" do
40
+ it "returns vehicles for route 22" do
41
+ result = CTA::BusTracker.vehicles!(:routes => 22)
42
+
43
+ expect(result).to be_instance_of(CTA::BusTracker::VehiclesResponse)
44
+ expect(result.vehicles.size).to eq(13)
45
+
46
+ expect(result.vehicles.first.route).to be_instance_of(CTA::Route)
47
+ expect(result.vehicles.first.route.route_id).to eq("22")
48
+ expect(result.vehicles.first.vehicle_id).to eq(4394)
49
+ expect(result.vehicles.first.pattern_distance).to eq(115)
50
+ end
51
+
52
+ it "returns information about one vehicle" do
53
+ result = CTA::BusTracker.vehicles!(:vehicles => 4394)
54
+
55
+ expect(result).to be_instance_of(CTA::BusTracker::VehiclesResponse)
56
+ expect(result.vehicles.size).to eq(1)
57
+ expect(result.vehicles.first.route).to be_instance_of(CTA::Route)
58
+ expect(result.vehicles.first.route.route_id).to eq("22")
59
+ expect(result.vehicles.first.vehicle_id).to eq(4394)
60
+ expect(result.vehicles.first.heading).to eq(359)
61
+ end
62
+ end
63
+
64
+ describe "routes!" do
65
+ it "returns information about all routes" do
66
+ result = CTA::BusTracker.routes!
67
+
68
+ expect(result).to be_instance_of(CTA::BusTracker::RoutesResponse)
69
+ expect(result.routes.size).to eq(127)
70
+ expect(result.routes.first.route_id).to eq("1")
71
+ end
72
+ end
73
+
74
+ describe "directions!" do
75
+ it "returns two directions for route 22" do
76
+ result = CTA::BusTracker.directions!(:route => 22)
77
+
78
+ expect(result).to be_instance_of(CTA::BusTracker::DirectionsResponse)
79
+ expect(result.directions.size).to eq(2)
80
+ expect(result.directions.first.direction).to eq("Northbound")
81
+ end
82
+ end
83
+
84
+ describe "stops!" do
85
+ it "returns information about Northbound route 22 stops" do
86
+ result = CTA::BusTracker.stops!(:route => 22, :direction => :northbound)
87
+
88
+ expect(result).to be_instance_of(CTA::BusTracker::StopsResponse)
89
+ expect(result.stops.size).to eq(86)
90
+ expect(result.stops.first).to be_instance_of(CTA::Stop)
91
+ expect(result.stops.first.stop_name).to eq("Clark & Addison")
92
+ end
93
+ end
94
+
95
+ describe "patterns!" do
96
+ it "returns information about patterns for route 22" do
97
+ result = CTA::BusTracker.patterns!(:route => 22)
98
+
99
+ expect(result).to be_instance_of(CTA::BusTracker::PatternsResponse)
100
+ expect(result.patterns.first.direction).to be_instance_of(CTA::BusTracker::Direction)
101
+ expect(result.patterns.first.id).to eq(3936)
102
+ expect(result.patterns.first.points.size).to eq(124)
103
+ expect(result.patterns.first.points.first.sequence).to eq(1)
104
+ expect(result.patterns.first.points.first.stop).to be_instance_of(CTA::Stop)
105
+ expect(result.patterns.first.points.first.stop.stop_id).to eq(14096)
106
+ expect(result.patterns.first.points[1].lat).to eq(42.019043088282)
107
+ expect(result.patterns.first.points[1].type).to eq(:waypoint)
108
+ expect(result.patterns.first.points[1].sequence).to eq(2)
109
+ end
110
+ end
111
+
112
+ describe "predictions!" do
113
+ it "returns predictions for route 22 stop 15898" do
114
+ result = CTA::BusTracker.predictions!(:routes => 22, :stops => 15895)
115
+
116
+ expect(result).to be_instance_of(CTA::BusTracker::PredictionsResponse)
117
+ expect(result.vehicles.size).to eq(2)
118
+ expect(result.vehicles.first.route.route_id).to eq("22")
119
+ expect(result.predictions.size).to eq(2)
120
+ expect(result.predictions.first.type).to eq("D")
121
+ expect(result.predictions.first.minutes).to eq(5)
122
+ expect(result.predictions.first.arrival_time.to_s).to eq("2015-02-14T12:25:00+00:00")
123
+ expect(result.predictions.first.delayed).to eq(false)
124
+ end
125
+
126
+ it "returns predictions for vehicle 4361" do
127
+ result = CTA::BusTracker.predictions!(:vehicles => 4361)
128
+
129
+ expect(result).to be_instance_of(CTA::BusTracker::PredictionsResponse)
130
+ expect(result.vehicles.size).to eq(30)
131
+ expect(result.vehicles.first.route.route_id).to eq("22")
132
+ expect(result.predictions.size).to eq(30)
133
+ expect(result.predictions.first.type).to eq("A")
134
+ expect(result.predictions.first.minutes).to eq(2)
135
+ expect(result.predictions.first.arrival_time.to_s).to eq("2015-02-14T12:38:00+00:00")
136
+ expect(result.predictions.first.delayed).to eq(false)
137
+ end
138
+ end
139
+
140
+ it "returns bulletins for route 8" do
141
+ result = CTA::BusTracker.bulletins!(:routes => 8)
142
+
143
+ expect(result).to be_instance_of(CTA::BusTracker::ServiceBulletinsResponse)
144
+ expect(result.bulletins.size).to eq(6)
145
+ expect(result.bulletins.first.subject).to eq("#8 Halsted reroute Halsted/35th")
146
+ expect(result.bulletins.first.affected_services.size).to eq(1)
147
+ expect(result.bulletins.first.affected_services.first.route.route_id).to eq("8")
148
+ end
149
+ end
@@ -0,0 +1,48 @@
1
+ base_path = File.expand_path(File.dirname(__FILE__))
2
+ data_path = File.expand_path(File.join(base_path, 'stubs'))
3
+
4
+ require File.join(base_path, "..", "lib", "cta_redux")
5
+
6
+ RSpec.describe CTA::TrainTracker do
7
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
8
+ stub.get('/api/1.0/routes.aspx?type=&routeid=&stationid=') { |env| [200, {}, File.read(File.join(data_path, 'routes_response.xml'))] }
9
+ stub.get('/api/1.0/routes.aspx?type=&routeid=8&stationid=') { |env| [200, {}, File.read(File.join(data_path, 'route_status8_response.xml'))] }
10
+ stub.get('/api/1.0/alerts.aspx') { |env| [200, {}, File.read(File.join(data_path, 'alerts_response.xml'))] }
11
+ end
12
+
13
+ CTA::CustomerAlerts.connection.instance_variable_get(:@builder).delete(Faraday::Adapter::NetHttp)
14
+ CTA::CustomerAlerts.connection.adapter :test, stubs
15
+
16
+ describe "status!" do
17
+ it "returns all status information" do
18
+ response = CTA::CustomerAlerts.status!
19
+
20
+ expect(response).to be_instance_of(CTA::CustomerAlerts::RouteStatusResponse)
21
+ expect(response.routes.size).to eq(140)
22
+ expect(response.routes.first.route.route_id).to eq("Red")
23
+ expect(response.routes.first.status).to eq("Planned Work w/Reroute")
24
+ end
25
+
26
+ it "returns status information for one route" do
27
+ response = CTA::CustomerAlerts.status!(:routes => 8)
28
+
29
+ expect(response).to be_instance_of(CTA::CustomerAlerts::RouteStatusResponse)
30
+ expect(response.routes.size).to eq(1)
31
+ expect(response.routes.first.route.route_id).to eq("8")
32
+ expect(response.routes.first.status).to eq("Bus Stop Relocation")
33
+ end
34
+ end
35
+
36
+ describe "alerts!" do
37
+ it "returns all alerts" do
38
+ response = CTA::CustomerAlerts.alerts!
39
+
40
+ expect(response).to be_instance_of(CTA::CustomerAlerts::AlertsResponse)
41
+ expect(response.alerts.size).to eq(44)
42
+ expect(response.alerts.first.alert_id).to eq(23322)
43
+ expect(response.alerts.first.category).to eq(:normal)
44
+ expect(response.alerts.first.major_alert).to eq(false)
45
+ expect(response.alerts.first.tbd).to eq(true)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ RSpec.configure do |config|
2
+ config.expect_with :rspec do |expectations|
3
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
4
+ end
5
+
6
+ config.mock_with :rspec do |mocks|
7
+ mocks.verify_partial_doubles = true
8
+ end
9
+
10
+ if config.files_to_run.one?
11
+ config.default_formatter = 'doc'
12
+ end
13
+
14
+ config.profile_examples = 10
15
+ config.order = :random
16
+ end