cta_redux 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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