rail_feeds 0.0.1

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 (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