rail_feeds 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +31 -0
  5. data/.travis.yml +26 -0
  6. data/CHANGELOG.md +3 -0
  7. data/Gemfile +6 -0
  8. data/Guardfile +25 -0
  9. data/LICENSE.md +32 -0
  10. data/README.md +77 -0
  11. data/Rakefile +3 -0
  12. data/doc/guides/Logging.md +13 -0
  13. data/doc/guides/Network Rail/CORPUS.md +34 -0
  14. data/doc/guides/Network Rail/SMART.md +39 -0
  15. data/doc/guides/Network Rail/Schedule.md +138 -0
  16. data/file +0 -0
  17. data/lib/rail_feeds/credentials.rb +45 -0
  18. data/lib/rail_feeds/logging.rb +51 -0
  19. data/lib/rail_feeds/network_rail/corpus.rb +77 -0
  20. data/lib/rail_feeds/network_rail/credentials.rb +22 -0
  21. data/lib/rail_feeds/network_rail/http_client.rb +57 -0
  22. data/lib/rail_feeds/network_rail/schedule/association.rb +208 -0
  23. data/lib/rail_feeds/network_rail/schedule/data.rb +215 -0
  24. data/lib/rail_feeds/network_rail/schedule/days.rb +95 -0
  25. data/lib/rail_feeds/network_rail/schedule/fetcher.rb +193 -0
  26. data/lib/rail_feeds/network_rail/schedule/header/cif.rb +102 -0
  27. data/lib/rail_feeds/network_rail/schedule/header/json.rb +79 -0
  28. data/lib/rail_feeds/network_rail/schedule/header.rb +22 -0
  29. data/lib/rail_feeds/network_rail/schedule/parser/cif.rb +141 -0
  30. data/lib/rail_feeds/network_rail/schedule/parser/json.rb +87 -0
  31. data/lib/rail_feeds/network_rail/schedule/parser.rb +108 -0
  32. data/lib/rail_feeds/network_rail/schedule/stp_indicator.rb +72 -0
  33. data/lib/rail_feeds/network_rail/schedule/tiploc.rb +100 -0
  34. data/lib/rail_feeds/network_rail/schedule/train_schedule/change_en_route.rb +158 -0
  35. data/lib/rail_feeds/network_rail/schedule/train_schedule/location/intermediate.rb +119 -0
  36. data/lib/rail_feeds/network_rail/schedule/train_schedule/location/origin.rb +91 -0
  37. data/lib/rail_feeds/network_rail/schedule/train_schedule/location/terminating.rb +72 -0
  38. data/lib/rail_feeds/network_rail/schedule/train_schedule/location.rb +76 -0
  39. data/lib/rail_feeds/network_rail/schedule/train_schedule.rb +392 -0
  40. data/lib/rail_feeds/network_rail/schedule.rb +33 -0
  41. data/lib/rail_feeds/network_rail/smart.rb +186 -0
  42. data/lib/rail_feeds/network_rail/stomp_client.rb +77 -0
  43. data/lib/rail_feeds/network_rail.rb +16 -0
  44. data/lib/rail_feeds/version.rb +14 -0
  45. data/lib/rail_feeds.rb +10 -0
  46. data/rail_feeds.gemspec +32 -0
  47. data/spec/fixtures/network_rail/schedule/data/full.yaml +60 -0
  48. data/spec/fixtures/network_rail/schedule/data/starting.yaml +131 -0
  49. data/spec/fixtures/network_rail/schedule/data/update-gap.yaml +10 -0
  50. data/spec/fixtures/network_rail/schedule/data/update-next.yaml +13 -0
  51. data/spec/fixtures/network_rail/schedule/data/update-old.yaml +10 -0
  52. data/spec/fixtures/network_rail/schedule/data/update.yaml +112 -0
  53. data/spec/fixtures/network_rail/schedule/parser/train_create.json +1 -0
  54. data/spec/fixtures/network_rail/schedule/parser/train_delete.json +1 -0
  55. data/spec/fixtures/network_rail/schedule/train_schedule/json-data.yaml +67 -0
  56. data/spec/rail_feeds/credentials_spec.rb +46 -0
  57. data/spec/rail_feeds/logging_spec.rb +81 -0
  58. data/spec/rail_feeds/network_rail/corpus_spec.rb +92 -0
  59. data/spec/rail_feeds/network_rail/credentials_spec.rb +22 -0
  60. data/spec/rail_feeds/network_rail/http_client_spec.rb +88 -0
  61. data/spec/rail_feeds/network_rail/schedule/association_spec.rb +205 -0
  62. data/spec/rail_feeds/network_rail/schedule/data_spec.rb +219 -0
  63. data/spec/rail_feeds/network_rail/schedule/days_shared.rb +99 -0
  64. data/spec/rail_feeds/network_rail/schedule/days_spec.rb +4 -0
  65. data/spec/rail_feeds/network_rail/schedule/fetcher_spec.rb +228 -0
  66. data/spec/rail_feeds/network_rail/schedule/header/cif_spec.rb +72 -0
  67. data/spec/rail_feeds/network_rail/schedule/header/json_spec.rb +51 -0
  68. data/spec/rail_feeds/network_rail/schedule/header_spec.rb +19 -0
  69. data/spec/rail_feeds/network_rail/schedule/parser/cif_spec.rb +197 -0
  70. data/spec/rail_feeds/network_rail/schedule/parser/json_spec.rb +172 -0
  71. data/spec/rail_feeds/network_rail/schedule/parser_spec.rb +34 -0
  72. data/spec/rail_feeds/network_rail/schedule/stp_indicator_shared.rb +49 -0
  73. data/spec/rail_feeds/network_rail/schedule/stp_indicator_spec.rb +4 -0
  74. data/spec/rail_feeds/network_rail/schedule/tiploc_spec.rb +77 -0
  75. data/spec/rail_feeds/network_rail/schedule/train_schedule/change_en_route_spec.rb +121 -0
  76. data/spec/rail_feeds/network_rail/schedule/train_schedule/location/intermediate_spec.rb +95 -0
  77. data/spec/rail_feeds/network_rail/schedule/train_schedule/location/origin_spec.rb +87 -0
  78. data/spec/rail_feeds/network_rail/schedule/train_schedule/location/terminating_spec.rb +81 -0
  79. data/spec/rail_feeds/network_rail/schedule/train_schedule/location_spec.rb +35 -0
  80. data/spec/rail_feeds/network_rail/schedule/train_schedule_spec.rb +284 -0
  81. data/spec/rail_feeds/network_rail/schedule_spec.rb +41 -0
  82. data/spec/rail_feeds/network_rail/smart_spec.rb +194 -0
  83. data/spec/rail_feeds/network_rail/stomp_client_spec.rb +151 -0
  84. data/spec/rail_feeds/network_rail_spec.rb +7 -0
  85. data/spec/rail_feeds_spec.rb +11 -0
  86. data/spec/spec_helper.rb +47 -0
  87. metadata +282 -0
@@ -0,0 +1 @@
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