rail_feeds 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/.rubocop.yml +31 -0
- data/.travis.yml +26 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +6 -0
- data/Guardfile +25 -0
- data/LICENSE.md +32 -0
- data/README.md +77 -0
- data/Rakefile +3 -0
- data/doc/guides/Logging.md +13 -0
- data/doc/guides/Network Rail/CORPUS.md +34 -0
- data/doc/guides/Network Rail/SMART.md +39 -0
- data/doc/guides/Network Rail/Schedule.md +138 -0
- data/file +0 -0
- data/lib/rail_feeds/credentials.rb +45 -0
- data/lib/rail_feeds/logging.rb +51 -0
- data/lib/rail_feeds/network_rail/corpus.rb +77 -0
- data/lib/rail_feeds/network_rail/credentials.rb +22 -0
- data/lib/rail_feeds/network_rail/http_client.rb +57 -0
- data/lib/rail_feeds/network_rail/schedule/association.rb +208 -0
- data/lib/rail_feeds/network_rail/schedule/data.rb +215 -0
- data/lib/rail_feeds/network_rail/schedule/days.rb +95 -0
- data/lib/rail_feeds/network_rail/schedule/fetcher.rb +193 -0
- data/lib/rail_feeds/network_rail/schedule/header/cif.rb +102 -0
- data/lib/rail_feeds/network_rail/schedule/header/json.rb +79 -0
- data/lib/rail_feeds/network_rail/schedule/header.rb +22 -0
- data/lib/rail_feeds/network_rail/schedule/parser/cif.rb +141 -0
- data/lib/rail_feeds/network_rail/schedule/parser/json.rb +87 -0
- data/lib/rail_feeds/network_rail/schedule/parser.rb +108 -0
- data/lib/rail_feeds/network_rail/schedule/stp_indicator.rb +72 -0
- data/lib/rail_feeds/network_rail/schedule/tiploc.rb +100 -0
- data/lib/rail_feeds/network_rail/schedule/train_schedule/change_en_route.rb +158 -0
- data/lib/rail_feeds/network_rail/schedule/train_schedule/location/intermediate.rb +119 -0
- data/lib/rail_feeds/network_rail/schedule/train_schedule/location/origin.rb +91 -0
- data/lib/rail_feeds/network_rail/schedule/train_schedule/location/terminating.rb +72 -0
- data/lib/rail_feeds/network_rail/schedule/train_schedule/location.rb +76 -0
- data/lib/rail_feeds/network_rail/schedule/train_schedule.rb +392 -0
- data/lib/rail_feeds/network_rail/schedule.rb +33 -0
- data/lib/rail_feeds/network_rail/smart.rb +186 -0
- data/lib/rail_feeds/network_rail/stomp_client.rb +77 -0
- data/lib/rail_feeds/network_rail.rb +16 -0
- data/lib/rail_feeds/version.rb +14 -0
- data/lib/rail_feeds.rb +10 -0
- data/rail_feeds.gemspec +32 -0
- data/spec/fixtures/network_rail/schedule/data/full.yaml +60 -0
- data/spec/fixtures/network_rail/schedule/data/starting.yaml +131 -0
- data/spec/fixtures/network_rail/schedule/data/update-gap.yaml +10 -0
- data/spec/fixtures/network_rail/schedule/data/update-next.yaml +13 -0
- data/spec/fixtures/network_rail/schedule/data/update-old.yaml +10 -0
- data/spec/fixtures/network_rail/schedule/data/update.yaml +112 -0
- data/spec/fixtures/network_rail/schedule/parser/train_create.json +1 -0
- data/spec/fixtures/network_rail/schedule/parser/train_delete.json +1 -0
- data/spec/fixtures/network_rail/schedule/train_schedule/json-data.yaml +67 -0
- data/spec/rail_feeds/credentials_spec.rb +46 -0
- data/spec/rail_feeds/logging_spec.rb +81 -0
- data/spec/rail_feeds/network_rail/corpus_spec.rb +92 -0
- data/spec/rail_feeds/network_rail/credentials_spec.rb +22 -0
- data/spec/rail_feeds/network_rail/http_client_spec.rb +88 -0
- data/spec/rail_feeds/network_rail/schedule/association_spec.rb +205 -0
- data/spec/rail_feeds/network_rail/schedule/data_spec.rb +219 -0
- data/spec/rail_feeds/network_rail/schedule/days_shared.rb +99 -0
- data/spec/rail_feeds/network_rail/schedule/days_spec.rb +4 -0
- data/spec/rail_feeds/network_rail/schedule/fetcher_spec.rb +228 -0
- data/spec/rail_feeds/network_rail/schedule/header/cif_spec.rb +72 -0
- data/spec/rail_feeds/network_rail/schedule/header/json_spec.rb +51 -0
- data/spec/rail_feeds/network_rail/schedule/header_spec.rb +19 -0
- data/spec/rail_feeds/network_rail/schedule/parser/cif_spec.rb +197 -0
- data/spec/rail_feeds/network_rail/schedule/parser/json_spec.rb +172 -0
- data/spec/rail_feeds/network_rail/schedule/parser_spec.rb +34 -0
- data/spec/rail_feeds/network_rail/schedule/stp_indicator_shared.rb +49 -0
- data/spec/rail_feeds/network_rail/schedule/stp_indicator_spec.rb +4 -0
- data/spec/rail_feeds/network_rail/schedule/tiploc_spec.rb +77 -0
- data/spec/rail_feeds/network_rail/schedule/train_schedule/change_en_route_spec.rb +121 -0
- data/spec/rail_feeds/network_rail/schedule/train_schedule/location/intermediate_spec.rb +95 -0
- data/spec/rail_feeds/network_rail/schedule/train_schedule/location/origin_spec.rb +87 -0
- data/spec/rail_feeds/network_rail/schedule/train_schedule/location/terminating_spec.rb +81 -0
- data/spec/rail_feeds/network_rail/schedule/train_schedule/location_spec.rb +35 -0
- data/spec/rail_feeds/network_rail/schedule/train_schedule_spec.rb +284 -0
- data/spec/rail_feeds/network_rail/schedule_spec.rb +41 -0
- data/spec/rail_feeds/network_rail/smart_spec.rb +194 -0
- data/spec/rail_feeds/network_rail/stomp_client_spec.rb +151 -0
- data/spec/rail_feeds/network_rail_spec.rb +7 -0
- data/spec/rail_feeds_spec.rb +11 -0
- data/spec/spec_helper.rb +47 -0
- 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,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
|