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