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 @@
1
+ {"JsonScheduleV1":{"CIF_bank_holiday_running":null,"CIF_stp_indicator":"P","CIF_train_uid":"W20309","applicable_timetable":"Y","atoc_code":"IL","new_schedule_segment":{"traction_class":"","uic_code":""},"schedule_days_runs":"1111100","schedule_end_date":"2018-12-07","schedule_segment":{"signalling_id":"2U10","CIF_train_category":"OO","CIF_headcode":"","CIF_course_indicator":1,"CIF_train_service_code":"24627006","CIF_business_sector":"??","CIF_power_type":"EMU","CIF_timing_load":"483","CIF_speed":"045","CIF_operating_characteristics":null,"CIF_train_class":"S","CIF_sleepers":null,"CIF_reservations":null,"CIF_connection_indicator":null,"CIF_catering_code":null,"CIF_service_branding":"","schedule_location":[{"location_type":"LO","record_identity":"LO","tiploc_code":"SHANKLN","tiploc_instance":null,"departure":"0718","public_departure":"0718","platform":null,"line":null,"engineering_allowance":null,"pathing_allowance":null,"performance_allowance":null},{"location_type":"LI","record_identity":"LI","tiploc_code":"LAKEIOW","tiploc_instance":null,"arrival":"0720","departure":"0721","pass":null,"public_arrival":"0720","public_departure":"0721","platform":null,"line":null,"path":null,"engineering_allowance":null,"pathing_allowance":null,"performance_allowance":null},{"location_type":"LI","record_identity":"LI","tiploc_code":"SNDOWN","tiploc_instance":null,"arrival":"0723","departure":"0724","pass":null,"public_arrival":"0723","public_departure":"0724","platform":null,"line":null,"path":null,"engineering_allowance":null,"pathing_allowance":null,"performance_allowance":null},{"location_type":"LI","record_identity":"LI","tiploc_code":"BRDING","tiploc_instance":null,"arrival":"0727H","departure":"0728","pass":null,"public_arrival":"0728","public_departure":"0728","platform":null,"line":null,"path":null,"engineering_allowance":null,"pathing_allowance":null,"performance_allowance":null},{"location_type":"LI","record_identity":"LI","tiploc_code":"SMALBRK","tiploc_instance":null,"arrival":null,"departure":null,"pass":"0732","public_arrival":null,"public_departure":null,"platform":null,"line":null,"path":null,"engineering_allowance":null,"pathing_allowance":null,"performance_allowance":null},{"location_type":"LI","record_identity":"LI","tiploc_code":"RYDS","tiploc_instance":null,"arrival":"0735","departure":"0735H","pass":null,"public_arrival":"0735","public_departure":"0735","platform":null,"line":null,"path":null,"engineering_allowance":null,"pathing_allowance":null,"performance_allowance":null},{"location_type":"LI","record_identity":"LI","tiploc_code":"RYDE","tiploc_instance":null,"arrival":"0738H","departure":"0740","pass":null,"public_arrival":"0739","public_departure":"0740","platform":null,"line":null,"path":null,"engineering_allowance":null,"pathing_allowance":null,"performance_allowance":null},{"location_type":"LT","record_identity":"LT","tiploc_code":"RYDP","tiploc_instance":null,"arrival":"0742","public_arrival":"0742","platform":null,"path":null}]},"schedule_start_date":"2018-05-21","train_status":"P","transaction_type":"Create"}}
@@ -0,0 +1 @@
1
+ {"JsonScheduleV1":{"CIF_train_uid":"C77605","schedule_start_date":"2018-08-25","CIF_stp_indicator":"O","transaction_type":"Delete"}}
@@ -0,0 +1,67 @@
1
+ ---
2
+ JsonScheduleV1:
3
+ CIF_bank_holiday_running: B
4
+ CIF_stp_indicator: P
5
+ CIF_train_uid: W20309
6
+ applicable_timetable: Y
7
+ atoc_code: IL
8
+ schedule_start_date: "2018-05-21"
9
+ train_status: S
10
+ transaction_type: Create
11
+ schedule_days_runs: "1111100"
12
+ schedule_end_date: "2018-12-07"
13
+ new_schedule_segment:
14
+ traction_class: ""
15
+ uic_code: "12345"
16
+ schedule_segment:
17
+ signalling_id: 2U10
18
+ CIF_train_category: "OO"
19
+ CIF_headcode: "1234"
20
+ CIF_course_indicator:
21
+ CIF_train_service_code: "24627006"
22
+ CIF_business_sector: ??
23
+ CIF_power_type: EMU
24
+ CIF_timing_load: "483"
25
+ CIF_speed: "045"
26
+ CIF_operating_characteristics: OPER-CHAR
27
+ CIF_train_class: S
28
+ CIF_sleepers: B
29
+ CIF_reservations: R
30
+ CIF_connection_indicator:
31
+ CIF_catering_code: C
32
+ CIF_service_branding: Br
33
+ schedule_location:
34
+ - location_type: LO
35
+ record_identity: LO
36
+ tiploc_code: SHANKLN
37
+ tiploc_instance:
38
+ departure: "0718"
39
+ public_departure: "0718"
40
+ platform: P
41
+ line: L
42
+ engineering_allowance: "1"
43
+ pathing_allowance: "1H"
44
+ performance_allowance: "2"
45
+ - location_type: LI
46
+ record_identity: LI
47
+ tiploc_code: LAKEIOW
48
+ tiploc_instance: nil
49
+ arrival: "0720"
50
+ departure: "0721"
51
+ pass: "0722"
52
+ public_arrival: "0720"
53
+ public_departure: "0721"
54
+ platform: P
55
+ line: L
56
+ path: p
57
+ engineering_allowance:
58
+ pathing_allowance:
59
+ performance_allowance:
60
+ - location_type: LT
61
+ record_identity: LT
62
+ tiploc_code: RYDP
63
+ tiploc_instance:
64
+ arrival: "0742"
65
+ public_arrival: "0742"
66
+ platform: P
67
+ path: p
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::Credentials do
4
+ context 'Using system wide credentials' do
5
+ before :each do
6
+ described_class.configure(
7
+ username: 'user@example.com',
8
+ password: 'P@55word'
9
+ )
10
+ end
11
+
12
+ it 'Sets class attributes' do
13
+ expect(described_class.username).to eq 'user@example.com'
14
+ expect(described_class.password).to eq 'P@55word'
15
+ end
16
+
17
+ it 'A new instance defaults to class attributes' do
18
+ expect(subject.username).to eq 'user@example.com'
19
+ expect(subject.password).to eq 'P@55word'
20
+ end
21
+ end
22
+
23
+
24
+ context 'Using specific credentials' do
25
+ subject do
26
+ described_class.new(
27
+ username: 'user2@example.com',
28
+ password: 'P@55word2'
29
+ )
30
+ end
31
+
32
+ before :each do
33
+ described_class.configure username: nil, password: nil
34
+ end
35
+
36
+ it 'Sets instance attributes' do
37
+ expect(subject.username).to eq 'user2@example.com'
38
+ expect(subject.password).to eq 'P@55word2'
39
+ end
40
+
41
+ it 'Leaves class attributes alone' do
42
+ expect(described_class.username).to eq ''
43
+ expect(described_class.password).to eq ''
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DummyClassForLoggerTests
4
+ include RailFeeds::Logging
5
+ end
6
+
7
+ describe RailFeeds::Logging do
8
+ let(:global_logger) { double Logger }
9
+
10
+ it 'Default system logger' do
11
+ described_class.logger = nil
12
+ formatter = double Proc
13
+ expect(described_class).to receive(:formatter).and_return(formatter)
14
+ expect(Logger).to receive(:new).with(
15
+ STDOUT,
16
+ formatter: formatter,
17
+ level: Logger::DEBUG
18
+ ).and_return(global_logger)
19
+ expect(described_class.logger).to be global_logger
20
+ end
21
+
22
+ describe 'Default system formatter' do
23
+ subject { described_class.formatter }
24
+ it 'With all items passed in' do
25
+ expect(subject.call('s', 'd', 'p', 'm')).to eq "d p s: m\n"
26
+ end
27
+
28
+ it 'Without progname' do
29
+ expect(subject.call('S', 'D', nil, 'M')).to eq "D S: M\n"
30
+ end
31
+ end
32
+
33
+ context 'included in a class' do
34
+ let(:klass) { DummyClassForLoggerTests }
35
+ let(:instance) { klass.new }
36
+ before(:each) do
37
+ allow(described_class).to receive(:logger).and_return(global_logger)
38
+ end
39
+ let(:class_logger) { double Logger }
40
+ let(:instance_logger) { double Logger }
41
+
42
+ describe 'Defaults to using global logger' do
43
+ it '::logger' do
44
+ expect(klass.logger).to be global_logger
45
+ end
46
+
47
+ it '#logger' do
48
+ expect(instance.logger).to be global_logger
49
+ end
50
+ end
51
+
52
+ describe 'Setting custom logger for class' do
53
+ before(:each) { klass.logger = class_logger }
54
+
55
+ it 'Global logger is unaffected' do
56
+ expect(described_class.logger).to be global_logger
57
+ end
58
+ it 'Instance defaults to using class logger' do
59
+ expect(instance.logger).to be class_logger
60
+ end
61
+ it 'Sets class logger' do
62
+ expect(klass.logger).to be class_logger
63
+ end
64
+ end
65
+
66
+ describe 'Setting custom logger for instance' do
67
+ before(:each) { klass.logger = class_logger }
68
+ before(:each) { instance.logger = instance_logger }
69
+
70
+ it 'Global logger is unaffected' do
71
+ expect(described_class.logger).to be global_logger
72
+ end
73
+ it 'Class logger is unaffected' do
74
+ expect(klass.logger).to be class_logger
75
+ end
76
+ it 'Sets instance logger' do
77
+ expect(instance.logger).to be instance_logger
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::CORPUS do
4
+ let :json do
5
+ '{"TIPLOCDATA":[{"NLCDESC":"MERSEYRAIL ELECTRICS","NLC":"8","TIPLOC":"MERSELE","3A' \
6
+ 'LPHA":"ABC","STANOX":"1","NLCDESC16":"MPTE HQ","UIC":"2"},{"NLCDESC":" ","NLC":" ' \
7
+ '","TIPLOC":" ","3ALPHA":" ","STANOX":" ","NLCDESC16":" ","UIC":" "}]}'
8
+ end
9
+ let(:http_client) { double RailFeeds::NetworkRail::HTTPClient }
10
+ let(:reader) { double Zlib::GzipReader }
11
+
12
+ describe '::download' do
13
+ let(:http_client) { double RailFeeds::NetworkRail::HTTPClient }
14
+ let(:temp_file) { double Tempfile }
15
+
16
+ it 'Using default credentials' do
17
+ expect(RailFeeds::NetworkRail::HTTPClient).to receive(:new)
18
+ .with(credentials: RailFeeds::NetworkRail::Credentials).and_return(http_client)
19
+ expect(http_client).to receive(:download)
20
+ .with('ntrod/SupportingFileAuthenticate?type=CORPUS', 'file')
21
+ described_class.download 'file'
22
+ end
23
+
24
+ it 'Using passed credentials' do
25
+ credentials = double RailFeeds::NetworkRail::Credentials
26
+ expect(RailFeeds::NetworkRail::HTTPClient).to receive(:new)
27
+ .with(credentials: credentials).and_return(http_client)
28
+ expect(http_client).to receive(:download)
29
+ described_class.download 'file', credentials: credentials
30
+ end
31
+ end
32
+
33
+ describe '::fetch' do
34
+ let(:http_client) { double RailFeeds::NetworkRail::HTTPClient }
35
+ let(:temp_file) { double Tempfile }
36
+
37
+ it 'Using default credentials' do
38
+ expect(RailFeeds::NetworkRail::HTTPClient).to receive(:new)
39
+ .with(credentials: RailFeeds::NetworkRail::Credentials).and_return(http_client)
40
+ expect(http_client).to receive(:fetch)
41
+ .with('ntrod/SupportingFileAuthenticate?type=CORPUS').and_return(temp_file)
42
+ expect(described_class.fetch).to eq temp_file
43
+ end
44
+
45
+ it 'Using passed credentials' do
46
+ credentials = double RailFeeds::NetworkRail::Credentials
47
+ expect(RailFeeds::NetworkRail::HTTPClient).to receive(:new)
48
+ .with(credentials: credentials).and_return(http_client)
49
+ expect(http_client).to receive(:fetch).and_return(temp_file)
50
+ expect(described_class.fetch(credentials: credentials)).to eq temp_file
51
+ end
52
+ end
53
+
54
+ describe '::load_file' do
55
+ it 'json file' do
56
+ expect(Zlib::GzipReader).to receive(:open).with('filename') { fail Zlib::GzipFile::Error }
57
+ expect(File).to receive(:read).with('filename').and_return(json)
58
+ data = described_class.load_file('filename')
59
+ expect(data[0].tiploc).to eq 'MERSELE'
60
+ expect(data[0].stanox).to eq 1
61
+ expect(data[0].crs).to eq 'ABC'
62
+ expect(data[0].uic).to eq 2
63
+ expect(data[0].nlc).to eq '8'
64
+ expect(data[0].nlc_description).to eq 'MERSEYRAIL ELECTRICS'
65
+ expect(data[0].nlc_short_description).to eq 'MPTE HQ'
66
+ expect(data[1].tiploc).to be_nil
67
+ expect(data[1].stanox).to be_nil
68
+ expect(data[1].crs).to be_nil
69
+ expect(data[1].uic).to be_nil
70
+ expect(data[1].nlc).to be_nil
71
+ expect(data[1].nlc_description).to be_nil
72
+ expect(data[1].nlc_short_description).to be_nil
73
+ end
74
+
75
+ it 'json.gz file' do
76
+ expect(Zlib::GzipReader).to receive(:open).with('filename')
77
+ .and_yield(StringIO.new(json))
78
+ expect(described_class.load_file('filename').count).to eq 2
79
+ end
80
+ end
81
+
82
+ it '::fetch_data' do
83
+ expect(RailFeeds::NetworkRail::HTTPClient)
84
+ .to receive(:new).with(credentials: RailFeeds::NetworkRail::Credentials)
85
+ .and_return(http_client)
86
+ expect(http_client).to receive(:fetch_unzipped)
87
+ .with('ntrod/SupportingFileAuthenticate?type=CORPUS')
88
+ .and_yield(reader)
89
+ expect(reader).to receive(:read).and_return(json)
90
+ expect(described_class.fetch_data.count).to eq 2
91
+ end
92
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::Credentials do
4
+ subject { described_class.new username: 'user-i', password: 'pass-i' }
5
+
6
+ it 'Has seperate values to RailFeeds::Credentials' do
7
+ described_class.configure username: 'a', password: 'b'
8
+ expect(described_class.username).to_not eq RailFeeds::Credentials.username
9
+ expect(described_class.password).to_not eq RailFeeds::Credentials.password
10
+ end
11
+
12
+ describe 'Outputs an array' do
13
+ it '::to_a' do
14
+ described_class.configure username: 'user', password: 'pass'
15
+ expect(described_class.to_a).to eq ['user', 'pass']
16
+ end
17
+
18
+ it '#to_a' do
19
+ expect(subject.to_a).to eq ['user-i', 'pass-i']
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::HTTPClient do
4
+ let(:temp_file) { double Tempfile }
5
+ let(:uri) { double URI }
6
+
7
+ describe '#download' do
8
+ let(:file) { double File }
9
+
10
+ it 'Saves the file' do
11
+ expect(URI).to receive(:parse).with('https://datafeeds.networkrail.co.uk/path').and_return(uri)
12
+ expect(uri).to receive(:open).and_return(temp_file)
13
+ expect(File).to receive(:open).with('file', 'w').and_yield(file)
14
+ expect(IO).to receive(:copy_stream).with(temp_file, file)
15
+ subject.download('path', 'file')
16
+ end
17
+
18
+ it 'Adds credentials when getting path' do
19
+ credentials = RailFeeds::NetworkRail::Credentials.new(
20
+ username: 'user',
21
+ password: 'pass'
22
+ )
23
+ expect(URI).to receive(:parse).and_return(uri)
24
+ expect(uri).to receive(:open)
25
+ .with(http_basic_authentication: ['user', 'pass'])
26
+ .and_return(temp_file)
27
+ expect(File).to receive(:open).and_yield(file)
28
+ expect(IO).to receive(:copy_stream).with(temp_file, file)
29
+ subject = described_class.new credentials: credentials
30
+ subject.download('path', 'file')
31
+ end
32
+
33
+ it 'Handles special characters in credentials' do
34
+ credentials = RailFeeds::NetworkRail::Credentials.new(
35
+ username: 'a@example.com',
36
+ password: '!:@'
37
+ )
38
+ expect(URI).to receive(:parse).and_return(uri)
39
+ expect(uri).to receive(:open).and_return(temp_file)
40
+ expect(File).to receive(:open).and_yield(file)
41
+ expect(IO).to receive(:copy_stream).with(temp_file, file)
42
+ subject = described_class.new credentials: credentials
43
+ expect { subject.download('path', 'file') }.to_not raise_error
44
+ end
45
+ end
46
+
47
+ describe '#fetch' do
48
+ it 'Yields what uri.open does' do
49
+ expect(URI).to receive(:parse).with('https://datafeeds.networkrail.co.uk/path').and_return(uri)
50
+ expect(uri).to receive(:open).and_return(temp_file)
51
+ expect { |a| subject.fetch('path', &a) }.to yield_with_args(temp_file)
52
+ end
53
+
54
+ it 'Adds credentials when getting path' do
55
+ credentials = RailFeeds::NetworkRail::Credentials.new(
56
+ username: 'user',
57
+ password: 'pass'
58
+ )
59
+ expect(URI).to receive(:parse).and_return(uri)
60
+ expect(uri).to receive(:open)
61
+ .with(http_basic_authentication: ['user', 'pass'])
62
+ .and_return(temp_file)
63
+ subject = described_class.new credentials: credentials
64
+ subject.fetch('path') {}
65
+ end
66
+
67
+ it 'Handles special characters in credentials' do
68
+ credentials = RailFeeds::NetworkRail::Credentials.new(
69
+ username: 'a@example.com',
70
+ password: '!:@'
71
+ )
72
+ expect(URI).to receive(:parse).and_return(uri)
73
+ expect(uri).to receive(:open).and_return(temp_file)
74
+ subject = described_class.new credentials: credentials
75
+ expect { subject.fetch('path') {} }.to_not raise_error
76
+ end
77
+ end
78
+
79
+ describe '#fetch_unzipped' do
80
+ it 'Returns what Zlib::GzipReader.open does' do
81
+ reader = double Zlib::GzipReader
82
+ expect(subject).to receive(:fetch).with('path').and_yield(temp_file)
83
+ expect(temp_file).to receive(:path).and_return('gz_file_path')
84
+ expect(Zlib::GzipReader).to receive(:open).with('gz_file_path').and_return(reader)
85
+ expect { |a| subject.fetch_unzipped('path', &a) }.to yield_with_args(reader)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NetworkRail::Schedule::Association do
4
+ let(:line) do
5
+ 'AANmmmmmmaaaaaa0102030405060101010AcDTTTTTTT12T P'
6
+ end
7
+ subject { described_class.from_cif line }
8
+
9
+ describe '::from_cif' do
10
+ it 'Sets attributes' do
11
+ expect(subject.main_train_uid).to eq 'mmmmmm'
12
+ expect(subject.associated_train_uid).to eq 'aaaaaa'
13
+ expect(subject.start_date).to eq Date.new(2001, 2, 3)
14
+ expect(subject.end_date).to eq Date.new(2004, 5, 6)
15
+ expect(subject.days).to eq [false, true, false, true, false, true, false]
16
+ expect(subject.category).to eq 'Ac'
17
+ expect(subject.date_indicator).to eq 'D'
18
+ expect(subject.tiploc).to eq 'TTTTTTT'
19
+ expect(subject.main_location_suffix).to eq 1
20
+ expect(subject.associated_location_suffix).to eq 2
21
+ expect(subject.stp_indicator).to eq :permanent
22
+ end
23
+
24
+ it 'Fails for invalid line' do
25
+ expect { described_class.from_cif('bad line') }
26
+ .to raise_error ArgumentError, "Invalid line:\nbad line"
27
+ end
28
+
29
+ it 'Delete line' do
30
+ line = 'AADW43767W43768180529 ASHFKY T C'
31
+ subject = described_class.from_cif line
32
+ expect(subject.main_train_uid).to eq 'W43767'
33
+ expect(subject.associated_train_uid).to eq 'W43768'
34
+ expect(subject.start_date).to eq Date.new(2018, 5, 29)
35
+ expect(subject.end_date).to be_nil
36
+ expect(subject.days).to eq [false, false, false, false, false, false, false]
37
+ expect(subject.category).to be_nil
38
+ expect(subject.date_indicator).to be_nil
39
+ expect(subject.tiploc).to eq 'ASHFKY'
40
+ expect(subject.main_location_suffix).to be_nil
41
+ expect(subject.associated_location_suffix).to be_nil
42
+ expect(subject.stp_indicator).to eq :stp_cancellation
43
+ end
44
+ end
45
+
46
+ it '::from_json' do
47
+ subject = described_class.from_json(
48
+ '{"JsonAssociationV1":{"transaction_type":"Create","main_train_uid":"C69888",' \
49
+ '"assoc_train_uid":"C69870","assoc_start_date":"2018-10-08T00:00:00Z",' \
50
+ '"assoc_end_date":"2018-12-07T00:00:00Z","assoc_days":"1111100","category":"NP",' \
51
+ '"date_indicator":"S","location":"NTNG","base_location_suffix":null,' \
52
+ '"assoc_location_suffix":null,"diagram_type":"T","CIF_stp_indicator":"P"}}'
53
+ )
54
+
55
+ expect(subject.main_train_uid).to eq 'C69888'
56
+ expect(subject.associated_train_uid).to eq 'C69870'
57
+ expect(subject.start_date).to eq Date.new(2018, 10, 8)
58
+ expect(subject.end_date).to eq Date.new(2018, 12, 7)
59
+ expect(subject.days).to eq [true, true, true, true, true, false, false]
60
+ expect(subject.category).to eq 'NP'
61
+ expect(subject.date_indicator).to eq 'S'
62
+ expect(subject.tiploc).to eq 'NTNG'
63
+ expect(subject.main_location_suffix).to be_nil
64
+ expect(subject.associated_location_suffix).to be_nil
65
+ expect(subject.stp_indicator).to eq :permanent
66
+ end
67
+
68
+ describe 'Helper methods' do
69
+ context 'Join association' do
70
+ before(:each) { subject.category = 'JJ' }
71
+ it { should be_join }
72
+ it { should_not be_divide }
73
+ it { should_not be_next }
74
+ end
75
+
76
+ context 'Divide association' do
77
+ before(:each) { subject.category = 'VV' }
78
+ it { should_not be_join }
79
+ it { should be_divide }
80
+ it { should_not be_next }
81
+ end
82
+
83
+ context 'Next association' do
84
+ before(:each) { subject.category = 'NP' }
85
+ it { should_not be_join }
86
+ it { should_not be_divide }
87
+ it { should be_next }
88
+ end
89
+
90
+ context 'Happens on the same day' do
91
+ before(:each) { subject.date_indicator = 'S' }
92
+ it { should be_same_day }
93
+ it { should_not be_over_next_midnight }
94
+ it { should_not be_over_previous_midnight }
95
+ end
96
+
97
+ context 'Happens over the following midnight' do
98
+ before(:each) { subject.date_indicator = 'N' }
99
+ it { should_not be_same_day }
100
+ it { should be_over_next_midnight }
101
+ it { should_not be_over_previous_midnight }
102
+ end
103
+
104
+ context 'Happens over the previous midnight' do
105
+ before(:each) { subject.date_indicator = 'P' }
106
+ it { should_not be_same_day }
107
+ it { should_not be_over_next_midnight }
108
+ it { should be_over_previous_midnight }
109
+ end
110
+
111
+ it '#main_train_event_id' do
112
+ subject.tiploc = 'TTTTTTT'
113
+ subject.main_location_suffix = 1
114
+ expect(subject.main_train_event_id).to eq 'TTTTTTT-1'
115
+ end
116
+
117
+ it '#associated_train_event_id' do
118
+ subject.tiploc = 'TTTTTTT'
119
+ subject.associated_location_suffix = 2
120
+ expect(subject.associated_train_event_id).to eq 'TTTTTTT-2'
121
+ end
122
+ end
123
+
124
+ it '#to_cif' do
125
+ expect(subject.to_cif).to eq "#{line}\n"
126
+ end
127
+
128
+ it '#to_json' do
129
+ expect(subject.to_json).to eq '{"JsonAssociationV1":{"transaction_type":"Create",' \
130
+ '"main_train_uid":"mmmmmm","assoc_train_uid":"aaaaaa",' \
131
+ '"assoc_start_date":"2001-02-03T00:00:00Z",' \
132
+ '"assoc_end_date":"2004-05-06T00:00:00Z",' \
133
+ '"assoc_days":"0101010","category":"Ac",' \
134
+ '"date_indicator":"D","location":"TTTTTTT",' \
135
+ '"base_location_suffix":1,"assoc_location_suffix":2,' \
136
+ '"diagram_type":"T","CIF_stp_indicator":"P"}}'
137
+ end
138
+
139
+ describe '#hash' do
140
+ it 'Uses tiploc, main_location_suffix and associated_location_suffix' do
141
+ subject.tiploc = 'TIPLOC'
142
+ subject.main_location_suffix = 1
143
+ subject.associated_location_suffix = 2
144
+ expect(subject.hash).to eq 'TIPLOC-1-2'
145
+ end
146
+ end
147
+
148
+ describe '#<=>' do
149
+ let(:association1) { described_class.new start_date: Date.new(2000, 1, 1) }
150
+ let(:association2) { described_class.new start_date: Date.new(2000, 1, 2) }
151
+
152
+ it 'Match' do
153
+ association2.start_date = Date.new 2000, 1, 1
154
+ expect(association1 <=> association2).to eq 0
155
+ expect(association2 <=> association1).to eq 0
156
+ end
157
+
158
+ it 'Doesn\'t match' do
159
+ expect(association1 <=> association2).to eq(-1)
160
+ expect(association2 <=> association1).to eq 1
161
+ end
162
+
163
+ it 'Compares to nil without error' do
164
+ expect { association1 <=> nil }.to_not raise_error
165
+ end
166
+ end
167
+
168
+ describe '#==' do
169
+ let(:other_association) { double described_class }
170
+ before :each do
171
+ subject.tiploc = 'TTTTTTT'
172
+ subject.main_location_suffix = 1
173
+ subject.associated_location_suffix = 2
174
+ allow(other_association).to receive(:main_train_event_id).and_return(nil)
175
+ allow(other_association).to receive(:associated_train_event_id).and_return(nil)
176
+ end
177
+
178
+ it 'Matches on neither main or associated train\'s event' do
179
+ expect(subject == other_association).to eq false
180
+ end
181
+
182
+ it 'Matches on main train\'s event only' do
183
+ allow(other_association).to receive(:main_train_event_id).and_return('TTTTTTT-1')
184
+ expect(subject == other_association).to eq false
185
+ end
186
+
187
+ it 'Matches on associated train\'s event only' do
188
+ allow(other_association).to receive(:associated_train_event_id).and_return('TTTTTTT-2')
189
+ expect(subject == other_association).to eq false
190
+ end
191
+
192
+ it 'Matches on both main and associated train\'s event' do
193
+ allow(other_association).to receive(:main_train_event_id).and_return('TTTTTTT-1')
194
+ allow(other_association).to receive(:associated_train_event_id).and_return('TTTTTTT-2')
195
+ expect(subject == other_association).to eq true
196
+ end
197
+
198
+ it 'Compares to nil without error' do
199
+ expect(subject).to_not eq nil
200
+ end
201
+ end
202
+
203
+ it_behaves_like 'it has an STP indicator'
204
+ it_behaves_like 'it has a days array'
205
+ end