rail_feeds 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +31 -0
  5. data/.travis.yml +26 -0
  6. data/CHANGELOG.md +3 -0
  7. data/Gemfile +6 -0
  8. data/Guardfile +25 -0
  9. data/LICENSE.md +32 -0
  10. data/README.md +77 -0
  11. data/Rakefile +3 -0
  12. data/doc/guides/Logging.md +13 -0
  13. data/doc/guides/Network Rail/CORPUS.md +34 -0
  14. data/doc/guides/Network Rail/SMART.md +39 -0
  15. data/doc/guides/Network Rail/Schedule.md +138 -0
  16. data/file +0 -0
  17. data/lib/rail_feeds/credentials.rb +45 -0
  18. data/lib/rail_feeds/logging.rb +51 -0
  19. data/lib/rail_feeds/network_rail/corpus.rb +77 -0
  20. data/lib/rail_feeds/network_rail/credentials.rb +22 -0
  21. data/lib/rail_feeds/network_rail/http_client.rb +57 -0
  22. data/lib/rail_feeds/network_rail/schedule/association.rb +208 -0
  23. data/lib/rail_feeds/network_rail/schedule/data.rb +215 -0
  24. data/lib/rail_feeds/network_rail/schedule/days.rb +95 -0
  25. data/lib/rail_feeds/network_rail/schedule/fetcher.rb +193 -0
  26. data/lib/rail_feeds/network_rail/schedule/header/cif.rb +102 -0
  27. data/lib/rail_feeds/network_rail/schedule/header/json.rb +79 -0
  28. data/lib/rail_feeds/network_rail/schedule/header.rb +22 -0
  29. data/lib/rail_feeds/network_rail/schedule/parser/cif.rb +141 -0
  30. data/lib/rail_feeds/network_rail/schedule/parser/json.rb +87 -0
  31. data/lib/rail_feeds/network_rail/schedule/parser.rb +108 -0
  32. data/lib/rail_feeds/network_rail/schedule/stp_indicator.rb +72 -0
  33. data/lib/rail_feeds/network_rail/schedule/tiploc.rb +100 -0
  34. data/lib/rail_feeds/network_rail/schedule/train_schedule/change_en_route.rb +158 -0
  35. data/lib/rail_feeds/network_rail/schedule/train_schedule/location/intermediate.rb +119 -0
  36. data/lib/rail_feeds/network_rail/schedule/train_schedule/location/origin.rb +91 -0
  37. data/lib/rail_feeds/network_rail/schedule/train_schedule/location/terminating.rb +72 -0
  38. data/lib/rail_feeds/network_rail/schedule/train_schedule/location.rb +76 -0
  39. data/lib/rail_feeds/network_rail/schedule/train_schedule.rb +392 -0
  40. data/lib/rail_feeds/network_rail/schedule.rb +33 -0
  41. data/lib/rail_feeds/network_rail/smart.rb +186 -0
  42. data/lib/rail_feeds/network_rail/stomp_client.rb +77 -0
  43. data/lib/rail_feeds/network_rail.rb +16 -0
  44. data/lib/rail_feeds/version.rb +14 -0
  45. data/lib/rail_feeds.rb +10 -0
  46. data/rail_feeds.gemspec +32 -0
  47. data/spec/fixtures/network_rail/schedule/data/full.yaml +60 -0
  48. data/spec/fixtures/network_rail/schedule/data/starting.yaml +131 -0
  49. data/spec/fixtures/network_rail/schedule/data/update-gap.yaml +10 -0
  50. data/spec/fixtures/network_rail/schedule/data/update-next.yaml +13 -0
  51. data/spec/fixtures/network_rail/schedule/data/update-old.yaml +10 -0
  52. data/spec/fixtures/network_rail/schedule/data/update.yaml +112 -0
  53. data/spec/fixtures/network_rail/schedule/parser/train_create.json +1 -0
  54. data/spec/fixtures/network_rail/schedule/parser/train_delete.json +1 -0
  55. data/spec/fixtures/network_rail/schedule/train_schedule/json-data.yaml +67 -0
  56. data/spec/rail_feeds/credentials_spec.rb +46 -0
  57. data/spec/rail_feeds/logging_spec.rb +81 -0
  58. data/spec/rail_feeds/network_rail/corpus_spec.rb +92 -0
  59. data/spec/rail_feeds/network_rail/credentials_spec.rb +22 -0
  60. data/spec/rail_feeds/network_rail/http_client_spec.rb +88 -0
  61. data/spec/rail_feeds/network_rail/schedule/association_spec.rb +205 -0
  62. data/spec/rail_feeds/network_rail/schedule/data_spec.rb +219 -0
  63. data/spec/rail_feeds/network_rail/schedule/days_shared.rb +99 -0
  64. data/spec/rail_feeds/network_rail/schedule/days_spec.rb +4 -0
  65. data/spec/rail_feeds/network_rail/schedule/fetcher_spec.rb +228 -0
  66. data/spec/rail_feeds/network_rail/schedule/header/cif_spec.rb +72 -0
  67. data/spec/rail_feeds/network_rail/schedule/header/json_spec.rb +51 -0
  68. data/spec/rail_feeds/network_rail/schedule/header_spec.rb +19 -0
  69. data/spec/rail_feeds/network_rail/schedule/parser/cif_spec.rb +197 -0
  70. data/spec/rail_feeds/network_rail/schedule/parser/json_spec.rb +172 -0
  71. data/spec/rail_feeds/network_rail/schedule/parser_spec.rb +34 -0
  72. data/spec/rail_feeds/network_rail/schedule/stp_indicator_shared.rb +49 -0
  73. data/spec/rail_feeds/network_rail/schedule/stp_indicator_spec.rb +4 -0
  74. data/spec/rail_feeds/network_rail/schedule/tiploc_spec.rb +77 -0
  75. data/spec/rail_feeds/network_rail/schedule/train_schedule/change_en_route_spec.rb +121 -0
  76. data/spec/rail_feeds/network_rail/schedule/train_schedule/location/intermediate_spec.rb +95 -0
  77. data/spec/rail_feeds/network_rail/schedule/train_schedule/location/origin_spec.rb +87 -0
  78. data/spec/rail_feeds/network_rail/schedule/train_schedule/location/terminating_spec.rb +81 -0
  79. data/spec/rail_feeds/network_rail/schedule/train_schedule/location_spec.rb +35 -0
  80. data/spec/rail_feeds/network_rail/schedule/train_schedule_spec.rb +284 -0
  81. data/spec/rail_feeds/network_rail/schedule_spec.rb +41 -0
  82. data/spec/rail_feeds/network_rail/smart_spec.rb +194 -0
  83. data/spec/rail_feeds/network_rail/stomp_client_spec.rb +151 -0
  84. data/spec/rail_feeds/network_rail_spec.rb +7 -0
  85. data/spec/rail_feeds_spec.rb +11 -0
  86. data/spec/spec_helper.rb +47 -0
  87. metadata +282 -0
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::Schedule::Header do
4
+ it '::from_cif' do
5
+ line = double String
6
+ header = double RailFeeds::NetworkRail::Schedule::Header::CIF
7
+ expect(RailFeeds::NetworkRail::Schedule::Header::CIF)
8
+ .to receive(:from_cif).with(line).and_return(header)
9
+ expect(described_class.from_cif(line)).to eq header
10
+ end
11
+
12
+ it '::from_json' do
13
+ line = double String
14
+ header = double RailFeeds::NetworkRail::Schedule::Header::JSON
15
+ expect(RailFeeds::NetworkRail::Schedule::Header::JSON)
16
+ .to receive(:from_json).with(line).and_return(header)
17
+ expect(described_class.from_json(line)).to eq header
18
+ end
19
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::Schedule::Parser::CIF do
4
+ let(:on_header_proc) { proc { fail 'Called on_header_proc!' } }
5
+ let(:on_trailer_proc) { proc { fail 'Called on_trailer_proc!' } }
6
+ let(:on_tiploc_create_proc) { proc { fail 'Called on_tiploc_create_proc!' } }
7
+ let(:on_tiploc_update_proc) { proc { fail 'Called on_tiploc_update_proc!' } }
8
+ let(:on_tiploc_delete_proc) { proc { fail 'Called on_tiploc_delete_proc!' } }
9
+ let(:on_association_create_proc) { proc { fail 'Called on_association_create_proc!' } }
10
+ let(:on_association_update_proc) { proc { fail 'Called on_association_update_proc!' } }
11
+ let(:on_association_delete_proc) { proc { fail 'Called on_association_delete_proc!' } }
12
+ let(:on_train_schedule_create_proc) { proc { fail 'Called on_train_schedule_create_proc!' } }
13
+ let(:on_train_schedule_update_proc) { proc { fail 'Called on_train_schedule_update_proc!' } }
14
+ let(:on_train_schedule_delete_proc) { proc { fail 'Called on_train_schedule_delete_proc!' } }
15
+ let(:on_comment_proc) { proc { fail 'Called on_comment_proc!' } }
16
+ subject do
17
+ described_class.new(
18
+ on_header: on_header_proc,
19
+ on_trailer: on_trailer_proc,
20
+ on_tiploc_create: on_tiploc_create_proc,
21
+ on_tiploc_update: on_tiploc_update_proc,
22
+ on_tiploc_delete: on_tiploc_delete_proc,
23
+ on_association_create: on_association_create_proc,
24
+ on_association_update: on_association_update_proc,
25
+ on_association_delete: on_association_delete_proc,
26
+ on_train_schedule_create: on_train_schedule_create_proc,
27
+ on_train_schedule_update: on_train_schedule_update_proc,
28
+ on_train_schedule_delete: on_train_schedule_delete_proc,
29
+ on_comment: on_comment_proc
30
+ )
31
+ end
32
+
33
+ let(:header_line) do
34
+ "HDTPS.UDFROC1.PD1806080806181950DFROC2Q FA080618080619 \n"
35
+ end
36
+ let(:comment_line) { "/this is a comment\n" }
37
+ let(:trailer_line) { "ZZ#{' ' * 78}\n" }
38
+ let(:tiploc_create_line) { "TI#{' ' * 78}\n" }
39
+ let(:tiploc_update_line) { "TA#{' ' * 78}\n" }
40
+ let(:tiploc_delete_line) { "TD#{' ' * 78}\n" }
41
+ let(:association_create_line) do
42
+ "AAN 0102030405060101010 P\n"
43
+ end
44
+ let(:association_update_line) do
45
+ "AAR 0102030405060101010 P\n"
46
+ end
47
+ let(:association_delete_line) do
48
+ "AAD 0102030405060101010 P\n"
49
+ end
50
+ let(:train_schedule_create_lines) do
51
+ [
52
+ format('%-79s', 'BSN 010203040506') + 'P',
53
+ format('%-80s', 'BX'),
54
+ format('%-80s', 'LO'),
55
+ format('%-80s', 'LI'),
56
+ format('%-80s', 'LT'),
57
+ format('%-79s', 'BSN 020304070809') + 'P',
58
+ format('%-80s', 'BX'),
59
+ format('%-80s', 'LO'),
60
+ format('%-80s', 'LT')
61
+ ].join("\n") + "\n"
62
+ end
63
+ let(:train_schedule_update_lines) do
64
+ [
65
+ format('%-79s', 'BSR 010203040506') + 'P',
66
+ format('%-80s', 'BX'),
67
+ format('%-80s', 'LO'),
68
+ format('%-80s', 'LI'),
69
+ format('%-80s', 'LT'),
70
+ format('%-79s', 'BSR 020304070809') + 'P',
71
+ format('%-80s', 'BX'),
72
+ format('%-80s', 'LO'),
73
+ format('%-80s', 'LT')
74
+ ].join("\n") + "\n"
75
+ end
76
+ let(:train_schedule_delete_line) { format('%-79.79s', 'BSD 010203040506') + "P\n" }
77
+
78
+ let(:smallest_file) { StringIO.new header_line + trailer_line }
79
+
80
+ describe '#parse_line' do
81
+ it 'Calls on_header proc' do
82
+ expect(on_header_proc).to receive(:call).with(
83
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
84
+ instance_of(RailFeeds::NetworkRail::Schedule::Header::CIF)
85
+ )
86
+ subject.parse_line header_line
87
+ end
88
+
89
+ it 'Calls on_trailer proc' do
90
+ expect(on_trailer_proc).to receive(:call)
91
+ .with(instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF))
92
+ subject.parse_line trailer_line
93
+ end
94
+
95
+ it 'Calls on_tiploc_create proc' do
96
+ expect(on_tiploc_create_proc).to receive(:call).with(
97
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
98
+ instance_of(RailFeeds::NetworkRail::Schedule::Tiploc)
99
+ )
100
+ subject.parse_line tiploc_create_line
101
+ end
102
+
103
+ it 'Calls on_tiploc_update proc' do
104
+ expect(on_tiploc_update_proc).to receive(:call).with(
105
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
106
+ instance_of(String),
107
+ instance_of(RailFeeds::NetworkRail::Schedule::Tiploc)
108
+ )
109
+ subject.parse_line tiploc_update_line
110
+ end
111
+
112
+ it 'Calls on_tiploc_delete proc' do
113
+ expect(on_tiploc_delete_proc).to receive(:call).with(
114
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
115
+ instance_of(String)
116
+ )
117
+ subject.parse_line tiploc_delete_line
118
+ end
119
+
120
+ it 'Calls on_association_create proc' do
121
+ expect(on_association_create_proc).to receive(:call).with(
122
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
123
+ instance_of(RailFeeds::NetworkRail::Schedule::Association)
124
+ )
125
+ subject.parse_line association_create_line
126
+ end
127
+
128
+ it 'Calls on_association_update proc' do
129
+ expect(on_association_update_proc).to receive(:call).with(
130
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
131
+ instance_of(RailFeeds::NetworkRail::Schedule::Association)
132
+ )
133
+ subject.parse_line association_update_line
134
+ end
135
+
136
+ it 'Calls on_association_delete proc' do
137
+ expect(on_association_delete_proc).to receive(:call).with(
138
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
139
+ instance_of(RailFeeds::NetworkRail::Schedule::Association)
140
+ )
141
+ subject.parse_line association_delete_line
142
+ end
143
+
144
+ it 'Calls on_train_delete proc' do
145
+ expect(on_train_schedule_delete_proc).to receive(:call).with(
146
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
147
+ instance_of(RailFeeds::NetworkRail::Schedule::TrainSchedule)
148
+ )
149
+ subject.parse_line train_schedule_delete_line
150
+ end
151
+
152
+ it 'Calls on_comment proc' do
153
+ expect(on_comment_proc).to receive(:call).with(
154
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
155
+ 'this is a comment'
156
+ )
157
+ subject.parse_line comment_line
158
+ end
159
+
160
+ it 'Logs and ignores bad line' do
161
+ logger = double Logger
162
+ allow(described_class).to receive(:logger).and_return(logger)
163
+ allow(logger).to receive(:debug)
164
+ allow(logger).to receive(:info)
165
+ expect(logger).to receive(:error).with('Can\'t understand line: "XXXX"')
166
+ expect { subject.parse_line "XXXX\n" }.to_not raise_error
167
+ end
168
+ end
169
+
170
+ describe '#parse_file' do
171
+ it 'Calls on_train_create proc' do
172
+ allow(on_trailer_proc).to receive(:call)
173
+ expect(on_train_schedule_create_proc).to receive(:call).with(
174
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
175
+ instance_of(RailFeeds::NetworkRail::Schedule::TrainSchedule)
176
+ ).twice
177
+ subject.parse_file StringIO.new(train_schedule_create_lines + trailer_line)
178
+ end
179
+
180
+ it 'Calls on_train_update proc' do
181
+ allow(on_trailer_proc).to receive(:call)
182
+ expect(on_train_schedule_update_proc).to receive(:call).with(
183
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::CIF),
184
+ instance_of(RailFeeds::NetworkRail::Schedule::TrainSchedule)
185
+ ).twice
186
+ subject.parse_file StringIO.new(train_schedule_update_lines + trailer_line)
187
+ end
188
+
189
+ it 'Created trains have locations' do
190
+ trains = []
191
+ train_proc = proc { |_parser, train| trains.push train }
192
+ subject = described_class.new(on_train_schedule_create: train_proc)
193
+ subject.parse_file StringIO.new(header_line + train_schedule_create_lines + trailer_line)
194
+ expect(trains.map { |t| t.journey.count }).to eq [3, 2]
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::Schedule::Parser::JSON do
4
+ let(:on_header_proc) { proc { fail 'Called on_header_proc!' } }
5
+ let(:on_trailer_proc) { proc { fail 'Called on_trailer_proc!' } }
6
+ let(:on_tiploc_create_proc) { proc { fail 'Called on_tiploc_create_proc!' } }
7
+ let(:on_tiploc_update_proc) { proc { fail 'Called on_tiploc_update_proc!' } }
8
+ let(:on_tiploc_delete_proc) { proc { fail 'Called on_tiploc_delete_proc!' } }
9
+ let(:on_association_create_proc) { proc { fail 'Called on_association_create_proc!' } }
10
+ let(:on_association_update_proc) { proc { fail 'Called on_association_update_proc!' } }
11
+ let(:on_association_delete_proc) { proc { fail 'Called on_association_delete_proc!' } }
12
+ let(:on_train_schedule_create_proc) { proc { fail 'Called on_train_schedule_create_proc!' } }
13
+ let(:on_train_schedule_update_proc) { proc { fail 'Called on_train_schedule_update_proc!' } }
14
+ let(:on_train_schedule_delete_proc) { proc { fail 'Called on_train_schedule_delete_proc!' } }
15
+ let(:on_comment_proc) { proc { fail 'Called on_comment_proc!' } }
16
+ subject do
17
+ described_class.new(
18
+ on_header: on_header_proc,
19
+ on_trailer: on_trailer_proc,
20
+ on_tiploc_create: on_tiploc_create_proc,
21
+ on_tiploc_update: on_tiploc_update_proc,
22
+ on_tiploc_delete: on_tiploc_delete_proc,
23
+ on_association_create: on_association_create_proc,
24
+ on_association_update: on_association_update_proc,
25
+ on_association_delete: on_association_delete_proc,
26
+ on_train_schedule_create: on_train_schedule_create_proc,
27
+ on_train_schedule_update: on_train_schedule_update_proc,
28
+ on_train_schedule_delete: on_train_schedule_delete_proc,
29
+ on_comment: on_comment_proc
30
+ )
31
+ end
32
+
33
+ describe '#parse_line' do
34
+ it 'Calls on_header proc' do
35
+ line = '{"JsonTimetableV1":{"classification":"public","timestamp":1530659402,' \
36
+ '"owner":"Network Rail","Sender":{"organisation":"Rockshore",' \
37
+ '"application":"NTROD","component":"SCHEDULE"},"Metadata":{"type":' \
38
+ '"full","sequence":2211}}}'
39
+ expect(on_header_proc).to receive(:call).with(
40
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::JSON),
41
+ instance_of(RailFeeds::NetworkRail::Schedule::Header::JSON)
42
+ )
43
+ subject.parse_line line
44
+ end
45
+
46
+ it 'Calls on_trailer proc' do
47
+ line = '{"EOF":true}'
48
+ expect(on_trailer_proc).to receive(:call)
49
+ .with(instance_of(RailFeeds::NetworkRail::Schedule::Parser::JSON))
50
+ subject.parse_line line
51
+ end
52
+
53
+ it 'Calls on_tiploc_create proc' do
54
+ line = '{"TiplocV1":{"transaction_type":"Create","tiploc_code":"SCAREXS","nalco"' \
55
+ ':"818502","stanox":"16203","crs_code":null,"description":null,' \
56
+ '"tps_description":"SCARBOROUGH EXCURSION SDGS"}}'
57
+ expect(on_tiploc_create_proc).to receive(:call).with(
58
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::JSON),
59
+ instance_of(RailFeeds::NetworkRail::Schedule::Tiploc)
60
+ )
61
+ subject.parse_line line
62
+ end
63
+
64
+ it 'Calls on_tiploc_delete proc' do
65
+ line = '{"TiplocV1":{"transaction_type":"Delete","tiploc_code":"SCAREXS","nalco"' \
66
+ ':"818502","stanox":"16203","crs_code":null,"description":null,' \
67
+ '"tps_description":"SCARBOROUGH EXCURSION SDGS"}}'
68
+ expect(on_tiploc_delete_proc).to receive(:call).with(
69
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::JSON),
70
+ instance_of(String)
71
+ )
72
+ subject.parse_line line
73
+ end
74
+
75
+ it 'Unknown tiploc action' do
76
+ logger = double Logger
77
+ line = '{"TiplocV1":{"transaction_type":"Unknown"}}'
78
+ allow(described_class).to receive(:logger).and_return(logger)
79
+ allow(logger).to receive(:debug)
80
+ allow(logger).to receive(:info)
81
+ expect(logger).to receive(:error)
82
+ .with("Don't know how to \"Unknown\" a Tiploc: #{line}")
83
+ expect { subject.parse_line line }.to_not raise_error
84
+ end
85
+
86
+ it 'Calls on_association_create proc' do
87
+ line = '{"JsonAssociationV1":{"transaction_type":"Create","main_train_uid":' \
88
+ '"C66471","assoc_train_uid":"C65170","assoc_start_date":' \
89
+ '"2018-08-05T00:00:00Z","assoc_end_date":"2018-09-02T00:00:00Z",' \
90
+ '"assoc_days":"0000001","category":"NP","date_indicator":"S","location"' \
91
+ ':"NTNG","base_location_suffix":null,"assoc_location_suffix":null,' \
92
+ '"diagram_type":"T","CIF_stp_indicator":"P"}}'
93
+ expect(on_association_create_proc).to receive(:call).with(
94
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::JSON),
95
+ instance_of(RailFeeds::NetworkRail::Schedule::Association)
96
+ )
97
+ subject.parse_line line
98
+ end
99
+
100
+ it 'Calls on_association_delete proc' do
101
+ line = '{"JsonAssociationV1":{"transaction_type":"Delete","main_train_uid":' \
102
+ '"L10610","assoc_train_uid":"L10543","assoc_start_date":' \
103
+ '"2018-07-01T00:00:00Z","location":"STPANCI","base_location_suffix":null' \
104
+ ',"diagram_type":"T","CIF_stp_indicator":"C"}}'
105
+ expect(on_association_delete_proc).to receive(:call).with(
106
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::JSON),
107
+ instance_of(RailFeeds::NetworkRail::Schedule::Association)
108
+ )
109
+ subject.parse_line line
110
+ end
111
+
112
+ it 'Unknown association action' do
113
+ logger = double Logger
114
+ line = '{"JsonAssociationV1":{"transaction_type":"Unknown"}}'
115
+ allow(described_class).to receive(:logger).and_return(logger)
116
+ allow(logger).to receive(:debug)
117
+ allow(logger).to receive(:info)
118
+ expect(logger).to receive(:error)
119
+ .with("Don't know how to \"Unknown\" an Association: #{line}")
120
+ expect { subject.parse_line line }.to_not raise_error
121
+ end
122
+
123
+
124
+ it 'Calls on_train_delete proc' do
125
+ expect(on_train_schedule_delete_proc).to receive(:call).with(
126
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::JSON),
127
+ instance_of(RailFeeds::NetworkRail::Schedule::TrainSchedule)
128
+ )
129
+ line = File.read(File.join(RSPEC_FIXTURES, 'network_rail', 'schedule', 'parser', 'train_delete.json'))
130
+ subject.parse_line line
131
+ end
132
+
133
+ it 'Calls on_train_create proc' do
134
+ allow(on_trailer_proc).to receive(:call)
135
+ expect(on_train_schedule_create_proc).to receive(:call).with(
136
+ instance_of(RailFeeds::NetworkRail::Schedule::Parser::JSON),
137
+ instance_of(RailFeeds::NetworkRail::Schedule::TrainSchedule)
138
+ )
139
+ line = File.read(File.join(RSPEC_FIXTURES, 'network_rail', 'schedule', 'parser', 'train_create.json'))
140
+ subject.parse_line line
141
+ end
142
+
143
+ it 'Unknown train action' do
144
+ logger = double Logger
145
+ line = '{"JsonScheduleV1":{"transaction_type":"Unknown"}}'
146
+ allow(described_class).to receive(:logger).and_return(logger)
147
+ allow(logger).to receive(:debug)
148
+ allow(logger).to receive(:info)
149
+ expect(logger).to receive(:error)
150
+ .with("Don't know how to \"Unknown\" a Train Schedule: #{line}")
151
+ expect { subject.parse_line line }.to_not raise_error
152
+ end
153
+
154
+ it 'Created trains have locations' do
155
+ trains = []
156
+ train_proc = proc { |_parser, train| trains.push train }
157
+ subject = described_class.new(on_train_schedule_create: train_proc)
158
+ line = File.read(File.join(RSPEC_FIXTURES, 'network_rail', 'schedule', 'parser', 'train_create.json'))
159
+ subject.parse_line String.new line
160
+ expect(trains.map { |t| t.journey.count }).to eq [8]
161
+ end
162
+
163
+ it 'Logs and ignores bad line' do
164
+ logger = double Logger
165
+ allow(described_class).to receive(:logger).and_return(logger)
166
+ allow(logger).to receive(:debug)
167
+ allow(logger).to receive(:info)
168
+ expect(logger).to receive(:error).with('Can\'t understand line: ["XXX"]')
169
+ expect { subject.parse_line '["XXX"]' }.to_not raise_error
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::Schedule::Parser do
4
+ describe '#parse_file' do
5
+ let(:file_content) { StringIO.new "1\n2\nEND\n" }
6
+
7
+ it 'Calls parse_line for each line' do
8
+ expect(subject).to receive(:parse_line).with("1\n")
9
+ expect(subject).to receive(:parse_line).with("2\n")
10
+ expect(subject).to receive(:parse_line).with("END\n") do
11
+ subject.instance_eval { @file_ended = true }
12
+ end
13
+ subject.parse_file file_content
14
+ end
15
+
16
+ it 'Skips the rest of the file when stop_parsing is called' do
17
+ expect(subject).to receive(:parse_line).with("1\n") { subject.stop_parsing }
18
+ expect(subject).to_not receive(:parse_line).with("2\n")
19
+ subject.parse_file file_content
20
+ end
21
+
22
+ it 'Fails when file is incomplete' do
23
+ incomplete_file = StringIO.new "1\n"
24
+ expect(subject).to receive(:parse_line).with("1\n")
25
+ expect { subject.parse_file incomplete_file }
26
+ .to raise_error RuntimeError, "File is incomplete. #{incomplete_file.inspect}"
27
+ end
28
+ end
29
+
30
+ it '#parse_line' do
31
+ expect { subject.parse_line '' }
32
+ .to raise_error RuntimeError, 'parse_file MUST be implemented in the child class.'
33
+ end
34
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'it has an STP indicator' do
4
+ describe 'Setting valid values' do
5
+ it ':permanent' do
6
+ subject.stp_indicator = :permanent
7
+ expect(subject.stp_indicator).to eq :permanent
8
+ end
9
+
10
+ it '"P"' do
11
+ subject.stp_indicator = 'P'
12
+ expect(subject.stp_indicator).to eq :permanent
13
+ end
14
+
15
+ it ':stp_new' do
16
+ subject.stp_indicator = :stp_new
17
+ expect(subject.stp_indicator).to eq :stp_new
18
+ end
19
+
20
+ it '"N"' do
21
+ subject.stp_indicator = 'N'
22
+ expect(subject.stp_indicator).to eq :stp_new
23
+ end
24
+
25
+ it ':stp_overlay' do
26
+ subject.stp_indicator = :stp_overlay
27
+ expect(subject.stp_indicator).to eq :stp_overlay
28
+ end
29
+
30
+ it '"O"' do
31
+ subject.stp_indicator = 'O'
32
+ expect(subject.stp_indicator).to eq :stp_overlay
33
+ end
34
+
35
+ it ':stp_cancellation' do
36
+ subject.stp_indicator = :stp_cancellation
37
+ expect(subject.stp_indicator).to eq :stp_cancellation
38
+ end
39
+
40
+ it '"C"' do
41
+ subject.stp_indicator = 'C'
42
+ expect(subject.stp_indicator).to eq :stp_cancellation
43
+ end
44
+ end
45
+
46
+ it 'Setting invalid values' do
47
+ expect { subject.stp_indicator = :invalid }.to raise_error ArgumentError, /^value \(:invalid\) is invalid, must be any of: /
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::Schedule::STPIndicator do
4
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::Schedule::Tiploc do
4
+ let(:line) do
5
+ 'TIttttttt 123456 TPS Description 54321 CrsNLC Description '
6
+ end
7
+ subject { described_class.from_cif line }
8
+
9
+ describe '::from_cif' do
10
+ it 'Sets attributes' do
11
+ expect(subject.tiploc).to eq 'ttttttt'
12
+ expect(subject.nlc).to eq 123456
13
+ expect(subject.stanox).to eq 54321
14
+ expect(subject.crs).to eq 'Crs'
15
+ expect(subject.nlc_description).to eq 'NLC Description'
16
+ expect(subject.tps_description).to eq 'TPS Description'
17
+ end
18
+
19
+ it 'Fails for invalid line' do
20
+ expect { described_class.from_cif('bad line') }
21
+ .to raise_error ArgumentError, "Invalid line:\nbad line"
22
+ end
23
+ end
24
+
25
+ it '::from_json' do
26
+ subject = described_class.from_json(
27
+ '{"TiplocV1":{"transaction_type":"Create","tiploc_code":"SCARTMD",' \
28
+ '"nalco":"818503","stanox":"16204","crs_code":"crs","description":"desc",' \
29
+ '"tps_description":"SCARBOROUGH TMD"}}'
30
+ )
31
+
32
+ expect(subject.tiploc).to eq 'SCARTMD'
33
+ expect(subject.nlc).to eq 818503
34
+ expect(subject.stanox).to eq 16204
35
+ expect(subject.crs).to eq 'crs'
36
+ expect(subject.nlc_description).to eq 'desc'
37
+ expect(subject.tps_description).to eq 'SCARBOROUGH TMD'
38
+ end
39
+
40
+ it '#to_cif' do
41
+ expect(subject.to_cif).to eq "#{line}\n"
42
+ end
43
+
44
+ it '#to_json' do
45
+ expect(subject.to_json).to eq '{"TiplocV1":{"transaction_type":"Create","tiploc_co' \
46
+ 'de":"ttttttt","nalco":"123456","stanox":"54321",' \
47
+ '"crs_code":"Crs","description":"NLC Description",' \
48
+ '"tps_description":"TPS Description"}}'
49
+ end
50
+
51
+ describe '#hash' do
52
+ it 'Uses tiploc' do
53
+ subject.tiploc = 'TIPLOC'
54
+ expect(subject.hash).to eq 'TIPLOC'
55
+ end
56
+ end
57
+
58
+ describe '#<=>' do
59
+ let(:tiploc1) { described_class.new tiploc: 'A' }
60
+ let(:tiploc2) { described_class.new tiploc: 'B' }
61
+
62
+ it 'Match' do
63
+ tiploc2.tiploc = 'A'
64
+ expect(tiploc1 <=> tiploc2).to eq 0
65
+ expect(tiploc2 <=> tiploc1).to eq 0
66
+ end
67
+
68
+ it 'Doesn\'t match' do
69
+ expect(tiploc1 <=> tiploc2).to eq(-1)
70
+ expect(tiploc2 <=> tiploc1).to eq 1
71
+ end
72
+
73
+ it 'Compares to nil without error' do
74
+ expect { tiploc1 <=> nil }.to_not raise_error
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::Schedule::TrainSchedule::ChangeEnRoute do
4
+ let(:line) do
5
+ 'CRttttttt4ccssss1111 22222222pPPPtttt333ooooooelr CCCCBBBB uuuuu '
6
+ end
7
+
8
+ subject do
9
+ described_class.new(
10
+ tiploc: 'ttttttt',
11
+ tiploc_suffix: 4,
12
+ category: 'cc',
13
+ signalling_headcode: 'ssss',
14
+ reservation_headcode: 1111,
15
+ service_code: 22222222,
16
+ portion_id: 'p',
17
+ power_type: 'PPP',
18
+ timing_load: 'tttt',
19
+ speed: 333,
20
+ operating_characteristics: 'oooooo',
21
+ seating_class: 'e',
22
+ sleeping_class: 'l',
23
+ reservations: 'r',
24
+ catering: 'CCCC',
25
+ branding: 'BBBB',
26
+ uic_code: 'uuuuu'
27
+ )
28
+ end
29
+
30
+ describe '::from_cif' do
31
+ it 'Sets attributes' do
32
+ subject = described_class.from_cif line
33
+
34
+ expect(subject.tiploc).to eq 'ttttttt'
35
+ expect(subject.tiploc_suffix).to eq 4
36
+ expect(subject.category).to eq 'cc'
37
+ expect(subject.signalling_headcode).to eq 'ssss'
38
+ expect(subject.reservation_headcode).to eq 1111
39
+ expect(subject.service_code).to eq 22222222
40
+ expect(subject.portion_id).to eq 'p'
41
+ expect(subject.power_type).to eq 'PPP'
42
+ expect(subject.timing_load).to eq 'tttt'
43
+ expect(subject.speed).to eq 333
44
+ expect(subject.operating_characteristics).to eq 'oooooo'
45
+ expect(subject.seating_class).to eq 'e'
46
+ expect(subject.sleeping_class).to eq 'l'
47
+ expect(subject.reservations).to eq 'r'
48
+ expect(subject.catering).to eq 'CCCC'
49
+ expect(subject.branding).to eq 'BBBB'
50
+ expect(subject.uic_code).to eq 'uuuuu'
51
+ end
52
+
53
+ it 'Fails for invalid line' do
54
+ expect { described_class.from_cif('bad line') }
55
+ .to raise_error ArgumentError, "Invalid line:\nbad line"
56
+ end
57
+ end
58
+
59
+ it '#apply_to' do
60
+ train = double RailFeeds::NetworkRail::Schedule::TrainSchedule
61
+ expect(train).to receive(:category=).with('cc')
62
+ expect(train).to receive(:signalling_headcode=).with('ssss')
63
+ expect(train).to receive(:reservation_headcode=).with(1111)
64
+ expect(train).to receive(:service_code=).with(22222222)
65
+ expect(train).to receive(:portion_id=).with('p')
66
+ expect(train).to receive(:power_type=).with('PPP')
67
+ expect(train).to receive(:timing_load=).with('tttt')
68
+ expect(train).to receive(:speed=).with(333)
69
+ expect(train).to receive(:operating_characteristics=).with('oooooo')
70
+ expect(train).to receive(:seating_class=).with('e')
71
+ expect(train).to receive(:sleeping_class=).with('l')
72
+ expect(train).to receive(:reservations=).with('r')
73
+ expect(train).to receive(:catering=).with('CCCC')
74
+ expect(train).to receive(:branding=).with('BBBB')
75
+ expect(train).to receive(:uic_code=).with('uuuuu')
76
+ subject.apply_to train
77
+ end
78
+
79
+ describe '#hash' do
80
+ it 'Uses tiploc and tiploc_suffix' do
81
+ expect(subject.hash).to eq 'ttttttt-4'
82
+ end
83
+ end
84
+
85
+ it '#to_cif' do
86
+ expect(subject.to_cif).to eq "#{line}\n"
87
+ end
88
+
89
+ it '#to_hash_for_json' do
90
+ expect(subject.to_hash_for_json).to be_nil
91
+ end
92
+
93
+ describe '#==' do
94
+ let(:change1) { described_class.new tiploc: 'a', tiploc_suffix: 1 }
95
+ let(:change2) { described_class.new tiploc: 'a', tiploc_suffix: 1 }
96
+
97
+ it 'Neither tiploc or tiploc_suffix match' do
98
+ change1.tiploc = 'b'
99
+ change1.tiploc_suffix = 2
100
+ expect(change1).to_not eq change2
101
+ end
102
+
103
+ it 'Tiploc matches but tiploc_suffix doesn\'t' do
104
+ change1.tiploc_suffix = 2
105
+ expect(change1).to_not eq change2
106
+ end
107
+
108
+ it 'Tiploc_suffix matches but tiploc doesn\'t' do
109
+ change1.tiploc = 'b'
110
+ expect(change1).to_not eq change2
111
+ end
112
+
113
+ it 'Both tiploc and tiploc_suffix match' do
114
+ expect(change1).to eq change2
115
+ end
116
+
117
+ it 'Compares to nil without error' do
118
+ expect(change1).to_not eq nil
119
+ end
120
+ end
121
+ end