rail_feeds 0.0.1 → 0.0.2

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -1
  3. data/.travis.yml +17 -4
  4. data/CHANGELOG.md +12 -0
  5. data/README.md +11 -7
  6. data/doc/guides/National Rail/Knowledge Base/National Service Indicator.md +21 -0
  7. data/doc/guides/Network Rail/CORPUS.md +7 -3
  8. data/doc/guides/Network Rail/SMART.md +6 -2
  9. data/doc/guides/Network Rail/Schedule.md +4 -4
  10. data/lib/rail_feeds/credentials.rb +30 -0
  11. data/lib/rail_feeds/http_client.rb +56 -0
  12. data/lib/rail_feeds/logging.rb +0 -2
  13. data/lib/rail_feeds/national_rail/credentials.rb +11 -0
  14. data/lib/rail_feeds/national_rail/http_client.rb +45 -0
  15. data/lib/rail_feeds/national_rail/knowledge_base/national_service_indicator.rb +100 -0
  16. data/lib/rail_feeds/national_rail/knowledge_base.rb +8 -0
  17. data/lib/rail_feeds/national_rail.rb +9 -0
  18. data/lib/rail_feeds/network_rail/corpus.rb +5 -6
  19. data/lib/rail_feeds/network_rail/credentials.rb +0 -11
  20. data/lib/rail_feeds/network_rail/http_client.rb +8 -44
  21. data/lib/rail_feeds/network_rail/schedule/association.rb +2 -2
  22. data/lib/rail_feeds/network_rail/schedule/data.rb +3 -2
  23. data/lib/rail_feeds/network_rail/schedule/days.rb +1 -0
  24. data/lib/rail_feeds/network_rail/schedule/fetcher.rb +1 -2
  25. data/lib/rail_feeds/network_rail/schedule/header/json.rb +2 -2
  26. data/lib/rail_feeds/network_rail/schedule/header.rb +0 -3
  27. data/lib/rail_feeds/network_rail/schedule/parser/json.rb +0 -2
  28. data/lib/rail_feeds/network_rail/schedule/parser.rb +1 -4
  29. data/lib/rail_feeds/network_rail/schedule/tiploc.rb +2 -2
  30. data/lib/rail_feeds/network_rail/schedule/train_schedule/location.rb +1 -4
  31. data/lib/rail_feeds/network_rail/schedule/train_schedule.rb +8 -12
  32. data/lib/rail_feeds/network_rail/schedule.rb +3 -10
  33. data/lib/rail_feeds/network_rail/smart.rb +17 -17
  34. data/lib/rail_feeds/network_rail/stomp_client.rb +2 -3
  35. data/lib/rail_feeds/network_rail.rb +0 -7
  36. data/lib/rail_feeds/version.rb +1 -1
  37. data/lib/rail_feeds.rb +40 -4
  38. data/rail_feeds.gemspec +28 -23
  39. data/spec/rail_feeds/credentials_spec.rb +28 -1
  40. data/spec/rail_feeds/http_client_spec.rb +75 -0
  41. data/spec/rail_feeds/national_rail/credentials_spec.rb +13 -0
  42. data/spec/rail_feeds/national_rail/http_client_spec.rb +57 -0
  43. data/spec/rail_feeds/national_rail/knowledge_base/national_service_indicator_spec.rb +122 -0
  44. data/spec/rail_feeds/national_rail/knowledge_base_spec.rb +4 -0
  45. data/spec/rail_feeds/national_rail_spec.rb +7 -0
  46. data/spec/rail_feeds/network_rail/corpus_spec.rb +2 -2
  47. data/spec/rail_feeds/network_rail/credentials_spec.rb +3 -12
  48. data/spec/rail_feeds/network_rail/http_client_spec.rb +7 -75
  49. data/spec/rail_feeds/network_rail/schedule/data_spec.rb +1 -1
  50. data/spec/rail_feeds/network_rail/smart_spec.rb +2 -2
  51. data/spec/rail_feeds/network_rail/stomp_client_spec.rb +1 -1
  52. metadata +48 -9
  53. data/file +0 -0
@@ -85,6 +85,7 @@ module RailFeeds
85
85
 
86
86
  def days_from_cif(value)
87
87
  return [nil, nil, nil, nil, nil, nil, nil] if value.nil?
88
+
88
89
  Array.new(7) { |i| value[i]&.eql?('1') }
89
90
  end
90
91
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
-
5
3
  module RailFeeds
6
4
  module NetworkRail
7
5
  module Schedule
@@ -177,6 +175,7 @@ module RailFeeds
177
175
  unless %w[mon tue wed thu fri sat sun].include?(day)
178
176
  fail ArgumentError, 'day is invalid'
179
177
  end
178
+
180
179
  day = "toc-update-#{day}"
181
180
  type = "CIF_#{toc}_UPDATE_DAILY"
182
181
  end
@@ -49,7 +49,7 @@ module RailFeeds
49
49
  end
50
50
 
51
51
  # rubocop:disable Metrics/MethodLength
52
- def to_json
52
+ def to_json(**opts)
53
53
  {
54
54
  'JsonTimetableV1' => {
55
55
  'classification' => 'public',
@@ -65,7 +65,7 @@ module RailFeeds
65
65
  'sequence' => sequence
66
66
  }
67
67
  }
68
- }.to_json
68
+ }.to_json(**opts)
69
69
  end
70
70
  # rubocop:enable Metrics/MethodLength
71
71
 
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'header/cif'
4
- require_relative 'header/json'
5
-
6
3
  module RailFeeds
7
4
  module NetworkRail
8
5
  module Schedule
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module RailFeeds
6
4
  module NetworkRail
7
5
  module Schedule
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'parser/cif'
4
- require_relative 'parser/json'
5
-
6
3
  module RailFeeds
7
4
  module NetworkRail
8
5
  module Schedule
@@ -75,7 +72,7 @@ module RailFeeds
75
72
  end
76
73
  # rubocop:enable Metrics/ParameterLists
77
74
 
78
- # Parse the data in CIF file.
75
+ # Parse the data in file.
79
76
  # @param [IO] file
80
77
  # The file to load data from.
81
78
  def parse_file(file)
@@ -81,7 +81,7 @@ module RailFeeds
81
81
  ].join) + "\n"
82
82
  end
83
83
 
84
- def to_json
84
+ def to_json(**opts)
85
85
  {
86
86
  'TiplocV1' => {
87
87
  'transaction_type' => 'Create',
@@ -92,7 +92,7 @@ module RailFeeds
92
92
  'description' => nlc_description,
93
93
  'tps_description' => tps_description
94
94
  }
95
- }.to_json
95
+ }.to_json(**opts)
96
96
  end
97
97
  end
98
98
  end
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'location/intermediate'
4
- require_relative 'location/origin'
5
- require_relative 'location/terminating'
6
-
7
3
  module RailFeeds
8
4
  module NetworkRail
9
5
  module Schedule
@@ -65,6 +61,7 @@ module RailFeeds
65
61
 
66
62
  def allowance_json(value)
67
63
  return nil if value.nil?
64
+
68
65
  i = value.to_i
69
66
  f = value.to_f - i
70
67
  f.eql?(0.5) ? "#{i}H" : i.to_s
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'date'
4
- require 'time'
5
-
6
- require_relative 'train_schedule/change_en_route'
7
- require_relative 'train_schedule/location'
8
-
9
3
  module RailFeeds
10
4
  module NetworkRail
11
5
  module Schedule
@@ -181,7 +175,7 @@ module RailFeeds
181
175
 
182
176
  # rubocop:disable Metrics/AbcSize
183
177
  # rubocop:disable Metrics/MethodLength
184
- def to_json
178
+ def to_json(**opts)
185
179
  {
186
180
  'JsonScheduleV1' => {
187
181
  'CIF_bank_holiday_running' => bank_holiday_running,
@@ -218,7 +212,7 @@ module RailFeeds
218
212
  'schedule_location' => journey.map(&:to_hash_for_json).reject(&:nil?)
219
213
  }
220
214
  }
221
- }.to_json
215
+ }.to_json(**opts)
222
216
  end
223
217
  # rubocop:enable Metrics/AbcSize
224
218
  # rubocop:enable Metrics/MethodLength
@@ -318,10 +312,11 @@ module RailFeeds
318
312
  end
319
313
 
320
314
  def self.location_from_json(hash)
321
- return lo_from_json(hash) if hash['record_identity'].eql?('LO')
322
- return li_from_json(hash) if hash['record_identity'].eql?('LI')
323
- return lt_from_json(hash) if hash['record_identity'].eql?('LT')
324
- nil
315
+ case hash['record_identity']
316
+ when 'LO' then lo_from_json(hash)
317
+ when 'LI' then li_from_json(hash)
318
+ when 'LT' then lt_from_json(hash)
319
+ end
325
320
  end
326
321
  private_class_method :location_from_json
327
322
 
@@ -380,6 +375,7 @@ module RailFeeds
380
375
 
381
376
  def self.parse_allowance(value)
382
377
  return nil if value.nil? || value.empty?
378
+
383
379
  half = value[-1].eql?('H')
384
380
  value = value.to_f
385
381
  half ? value + 0.5 : value
@@ -1,31 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'schedule/days'
4
- require_relative 'schedule/stp_indicator'
5
- require_relative 'schedule/association'
6
- require_relative 'schedule/header'
7
- require_relative 'schedule/tiploc'
8
- require_relative 'schedule/train_schedule'
9
- require_relative 'schedule/fetcher'
10
- require_relative 'schedule/parser'
11
- require_relative 'schedule/data'
12
-
13
3
  module RailFeeds
14
4
  module NetworkRail
15
5
  module Schedule # :nodoc:
16
6
  def self.nil_or_i(value)
17
7
  return nil if value.to_s.strip.empty?
8
+
18
9
  value.to_i
19
10
  end
20
11
 
21
12
  def self.nil_or_strip(value)
22
13
  return nil if value.to_s.strip.empty?
14
+
23
15
  value.strip
24
16
  end
25
17
 
26
18
  def self.make_date(value, allow_nil: false)
27
19
  return nil if allow_nil && value.strip.empty?
28
20
  return Date.new(9999, 12, 31) if value.eql?('999999')
21
+
29
22
  Date.strptime(value, '%y%m%d')
30
23
  end
31
24
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
- require 'set'
5
-
6
3
  module RailFeeds
7
4
  module NetworkRail
8
5
  # rubocop:disable Metrics/ModuleLength
@@ -16,6 +13,7 @@ module RailFeeds
16
13
  def from_direction
17
14
  return :up if event_direction.eql?(:down)
18
15
  return :down if event_direction.eql?(:up)
16
+
19
17
  nil
20
18
  end
21
19
 
@@ -32,15 +30,15 @@ module RailFeeds
32
30
  # @param [RailFeeds::NetworkRail::Credentials] credentials
33
31
  # @param [String] file
34
32
  # The path to the file to save the .json.gz download in.
35
- def self.download(file, credentials: Credentials)
33
+ def self.download(file, credentials = Credentials)
36
34
  client = HTTPClient.new(credentials: credentials)
37
35
  client.download 'ntrod/SupportingFileAuthenticate?type=SMART', file
38
36
  end
39
37
 
40
38
  # Fetch the current SMART data.
41
39
  # @param [RailFeeds::NetworkRail::Credentials] credentials
42
- # @return [Tempfile]
43
- def self.fetch(credentials: Credentials)
40
+ # @return [IO]
41
+ def self.fetch(credentials = Credentials)
44
42
  client = HTTPClient.new(credentials: credentials)
45
43
  client.fetch 'ntrod/SupportingFileAuthenticate?type=SMART'
46
44
  end
@@ -60,7 +58,7 @@ module RailFeeds
60
58
  # @param [RailFeeds::NetworkRail::Credentials] credentials
61
59
  # The credentials to authenticate with.
62
60
  # @return [Array<RailFeeds::NetworkRail::SMART::Step>]
63
- def self.fetch_data(credentials: Credentials)
61
+ def self.fetch_data(credentials = Credentials)
64
62
  client = HTTPClient.new(credentials: credentials)
65
63
  client.fetch_unzipped('ntrod/SupportingFileAuthenticate?type=SMART') do |file|
66
64
  break parse_json file.read
@@ -147,6 +145,7 @@ module RailFeeds
147
145
 
148
146
  def self.nilify(value)
149
147
  return nil if value.nil? || value.empty?
148
+
150
149
  value
151
150
  end
152
151
  private_class_method :nilify
@@ -154,6 +153,7 @@ module RailFeeds
154
153
  def self.event_type(value)
155
154
  return :arrive if value.eql?('A') || value.eql?('C')
156
155
  return :depart if value.eql?('B') || value.eql?('D')
156
+
157
157
  nil
158
158
  end
159
159
  private_class_method :event_type
@@ -161,24 +161,24 @@ module RailFeeds
161
161
  def self.event_direction(value)
162
162
  return :up if value.eql?('A') || value.eql?('B')
163
163
  return :down if value.eql?('C') || value.eql?('D')
164
+
164
165
  nil
165
166
  end
166
167
  private_class_method :event_direction
167
168
 
168
169
  # rubocop:disable Metrics/CyclomaticComplexity
169
- # rubocop:disable Metrics/PerceivedComplexity
170
170
  def self.step_type(value)
171
- return :between if value.eql?('B')
172
- return :from if value.eql?('F')
173
- return :to if value.eql?('T')
174
- return :intermediate_first if value.eql?('D')
175
- return :clearout if value.eql?('C')
176
- return :interpose if value.eql?('I')
177
- return :intermediate if value.eql?('E')
178
- nil
171
+ case value
172
+ when 'B' then :between
173
+ when 'F' then :from
174
+ when 'T' then :to
175
+ when 'D' then :intermediate_first
176
+ when 'E' then :intermediate
177
+ when 'C' then :clearout
178
+ when 'I' then :interpose
179
+ end
179
180
  end
180
181
  # rubocop:enable Metrics/CyclomaticComplexity
181
- # rubocop:enable Metrics/PerceivedComplexity
182
182
  private_class_method :step_type
183
183
  end
184
184
  # rubocop:enable Metrics/ModuleLength
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'socket'
4
- require 'stomp'
5
-
6
3
  module RailFeeds
7
4
  module NetworkRail
8
5
  # A wrapper class for ::Stomp::Client which provides durable subscriptions
@@ -27,6 +24,7 @@ module RailFeeds
27
24
  # Connect to the network rail server.
28
25
  def connect
29
26
  return if @client && client.open?
27
+
30
28
  client_options = {
31
29
  hosts: [{
32
30
  host: HOST,
@@ -49,6 +47,7 @@ module RailFeeds
49
47
  # Disconnect from the network rail server.
50
48
  def disconnect
51
49
  return if @client.nil?
50
+
52
51
  @client.close
53
52
  end
54
53
 
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'network_rail/credentials'
4
- require_relative 'network_rail/http_client'
5
- require_relative 'network_rail/stomp_client'
6
- require_relative 'network_rail/corpus'
7
- require_relative 'network_rail/schedule'
8
- require_relative 'network_rail/smart'
9
-
10
3
  module RailFeeds
11
4
  module NetworkRail # :nodoc:
12
5
  end
@@ -5,7 +5,7 @@ module RailFeeds
5
5
  class Version
6
6
  MAJOR = 0
7
7
  MINOR = 0
8
- PATCH = 1
8
+ PATCH = 2
9
9
 
10
10
  def self.to_s
11
11
  [MAJOR, MINOR, PATCH].join('.')
data/lib/rail_feeds.rb CHANGED
@@ -1,10 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ module RailFeeds # :nodoc:
4
+ end
5
+
6
+ require 'date'
7
+ require 'forwardable'
8
+ require 'logger'
9
+
10
+ require 'nokogiri'
11
+ require 'stomp'
4
12
 
13
+ require_relative 'rail_feeds/version'
5
14
  require_relative 'rail_feeds/logging'
6
15
  require_relative 'rail_feeds/credentials'
16
+ require_relative 'rail_feeds/http_client'
17
+ require_relative 'rail_feeds/national_rail'
18
+ require_relative 'rail_feeds/national_rail/credentials'
19
+ require_relative 'rail_feeds/national_rail/http_client'
20
+ require_relative 'rail_feeds/national_rail/knowledge_base'
21
+ require_relative 'rail_feeds/national_rail/knowledge_base/national_service_indicator'
7
22
  require_relative 'rail_feeds/network_rail'
8
-
9
- module RailFeeds # :nodoc:
10
- end
23
+ require_relative 'rail_feeds/network_rail/credentials'
24
+ require_relative 'rail_feeds/network_rail/http_client'
25
+ require_relative 'rail_feeds/network_rail/stomp_client'
26
+ require_relative 'rail_feeds/network_rail/corpus'
27
+ require_relative 'rail_feeds/network_rail/smart'
28
+ require_relative 'rail_feeds/network_rail/schedule'
29
+ require_relative 'rail_feeds/network_rail/schedule/days'
30
+ require_relative 'rail_feeds/network_rail/schedule/stp_indicator'
31
+ require_relative 'rail_feeds/network_rail/schedule/association'
32
+ require_relative 'rail_feeds/network_rail/schedule/data'
33
+ require_relative 'rail_feeds/network_rail/schedule/fetcher'
34
+ require_relative 'rail_feeds/network_rail/schedule/tiploc'
35
+ require_relative 'rail_feeds/network_rail/schedule/header'
36
+ require_relative 'rail_feeds/network_rail/schedule/header/cif'
37
+ require_relative 'rail_feeds/network_rail/schedule/header/json'
38
+ require_relative 'rail_feeds/network_rail/schedule/parser'
39
+ require_relative 'rail_feeds/network_rail/schedule/parser/cif'
40
+ require_relative 'rail_feeds/network_rail/schedule/parser/json'
41
+ require_relative 'rail_feeds/network_rail/schedule/train_schedule'
42
+ require_relative 'rail_feeds/network_rail/schedule/train_schedule/change_en_route'
43
+ require_relative 'rail_feeds/network_rail/schedule/train_schedule/location'
44
+ require_relative 'rail_feeds/network_rail/schedule/train_schedule/location/intermediate'
45
+ require_relative 'rail_feeds/network_rail/schedule/train_schedule/location/origin'
46
+ require_relative 'rail_feeds/network_rail/schedule/train_schedule/location/terminating'
data/rail_feeds.gemspec CHANGED
@@ -3,30 +3,35 @@
3
3
  $LOAD_PATH.push File.expand_path('lib', __dir__)
4
4
  require File.join(File.dirname(__FILE__), 'lib', 'rail_feeds', 'version')
5
5
 
6
- Gem::Specification.new do |s|
7
- s.name = 'rail_feeds'
8
- s.license = 'BSD 3 clause'
9
- s.version = RailFeeds::Version
10
- s.authors = ['Robert Gauld']
11
- s.email = ['robert@robertgauld.co.uk']
12
- s.homepage = 'https://github.com/robertgauld/rail_feeds'
13
- s.summary = 'Make use of the various open data rails feeds in the UK.'
14
- s.description = 'Make use of the various open data rails feeds in the UK. Currently only some from Network Rail.'
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'rail_feeds'
8
+ gem.license = 'BSD 3 clause'
9
+ gem.version = RailFeeds::Version
10
+ gem.authors = ['Robert Gauld']
11
+ gem.email = ['robert@robertgauld.co.uk']
12
+ gem.homepage = 'https://github.com/robertgauld/rail_feeds'
13
+ gem.summary = 'Make use of the various open data rails feeds in the UK.'
14
+ gem.description = 'Make use of the various open data rails feeds in the UK. Currently only some from Network Rail.'
15
15
 
16
- s.files = `git ls-files`.split("\n")
17
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
- s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
- s.require_paths = ['lib']
16
+ gem.files = `git ls-files`.split("\n")
17
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
+ gem.require_paths = ['lib']
20
20
 
21
- s.add_dependency 'stomp', '~> 1.4'
21
+ gem.required_ruby_version = '>= 2.4'
22
+ gem.required_rubygems_version = '>= 2.6.14'
22
23
 
23
- s.add_development_dependency 'coveralls', '~> 0.7'
24
- s.add_development_dependency 'guard-rspec', '~> 4.2', '>= 4.2.5'
25
- s.add_development_dependency 'guard-rubocop', '~> 1.3'
26
- s.add_development_dependency 'rake', '~> 12.0'
27
- s.add_development_dependency 'rb-inotify', '~> 0.9'
28
- s.add_development_dependency 'rspec', '>= 3.7', '< 4'
29
- s.add_development_dependency 'rubocop', '~> 0.57.1'
30
- s.add_development_dependency 'simplecov', '~> 0.7'
31
- s.add_development_dependency 'timecop', '~> 0.5'
24
+ gem.add_dependency 'nokogiri', '~> 1.8'
25
+ gem.add_dependency 'stomp', '~> 1.4'
26
+
27
+ gem.add_development_dependency 'coveralls', '~> 0.7'
28
+ gem.add_development_dependency 'guard-rspec', '~> 4.2', '>= 4.2.5'
29
+ gem.add_development_dependency 'guard-rubocop', '~> 1.3'
30
+ gem.add_development_dependency 'rake', '~> 12.0'
31
+ gem.add_development_dependency 'rb-inotify', '~> 0.9'
32
+ gem.add_development_dependency 'rspec', '>= 3.7', '< 4'
33
+ gem.add_development_dependency 'rubocop', '~> 0.67'
34
+ gem.add_development_dependency 'rubocop-performance', '~> 1.1'
35
+ gem.add_development_dependency 'simplecov', '~> 0.7'
36
+ gem.add_development_dependency 'timecop', '~> 0.5'
32
37
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  describe RailFeeds::Credentials do
4
4
  context 'Using system wide credentials' do
5
+ subject { described_class.new }
6
+
5
7
  before :each do
6
8
  described_class.configure(
7
9
  username: 'user@example.com',
@@ -20,7 +22,6 @@ describe RailFeeds::Credentials do
20
22
  end
21
23
  end
22
24
 
23
-
24
25
  context 'Using specific credentials' do
25
26
  subject do
26
27
  described_class.new(
@@ -43,4 +44,30 @@ describe RailFeeds::Credentials do
43
44
  expect(described_class.password).to eq ''
44
45
  end
45
46
  end
47
+
48
+ describe 'Outputs an array' do
49
+ subject { described_class.new username: 'user-i', password: 'pass-i' }
50
+
51
+ it '::to_a' do
52
+ described_class.configure username: 'user', password: 'pass'
53
+ expect(described_class.to_a).to eq ['user', 'pass']
54
+ end
55
+
56
+ it '#to_a' do
57
+ expect(subject.to_a).to eq ['user-i', 'pass-i']
58
+ end
59
+ end
60
+
61
+ describe 'Outputs a hash' do
62
+ subject { described_class.new username: 'user-i', password: 'pass-i' }
63
+
64
+ it '::to_h' do
65
+ described_class.configure username: 'user', password: 'pass'
66
+ expect(described_class.to_h).to eq({ username: 'user', password: 'pass' })
67
+ end
68
+
69
+ it '#to_h' do
70
+ expect(subject.to_h).to eq({ username: 'user-i', password: 'pass-i' })
71
+ end
72
+ end
46
73
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::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(subject).to receive(:fetch).with('url').and_yield(temp_file)
12
+ expect(File).to receive(:open).with('file', 'w').and_yield(file)
13
+ expect(IO).to receive(:copy_stream).with(temp_file, file)
14
+ subject.download('url', 'file')
15
+ end
16
+ end
17
+
18
+ describe '#fetch' do
19
+ describe 'Yields an IO' do
20
+ it 'When uri.open gives a TempFile' do
21
+ expect(URI).to receive(:parse).with('https://www.example.com/path').and_return(uri)
22
+ expect(uri).to receive(:open).and_return(temp_file)
23
+ expect { |a| subject.fetch('https://www.example.com/path', &a) }.to yield_with_args(temp_file)
24
+ end
25
+
26
+ it 'When uri.open gives a StringIO' do
27
+ string_io = double StringIO
28
+ expect(URI).to receive(:parse).with('https://www.example.com/path').and_return(uri)
29
+ expect(uri).to receive(:open).and_return(string_io)
30
+ expect { |a| subject.fetch('https://www.example.com/path', &a) }.to yield_with_args(string_io)
31
+ end
32
+ end
33
+
34
+ it 'Adds credentials when getting path' do
35
+ credentials = RailFeeds::NetworkRail::Credentials.new(
36
+ username: 'user',
37
+ password: 'pass'
38
+ )
39
+ expect(URI).to receive(:parse).and_return(uri)
40
+ expect(uri).to receive(:open)
41
+ .with(http_basic_authentication: ['user', 'pass'])
42
+ .and_return(temp_file)
43
+ subject = described_class.new credentials: credentials
44
+ subject.fetch('path') {}
45
+ end
46
+
47
+ it 'Handles special characters in credentials' do
48
+ credentials = RailFeeds::NetworkRail::Credentials.new(
49
+ username: 'a@example.com',
50
+ password: '!:@'
51
+ )
52
+ expect(URI).to receive(:parse).and_return(uri)
53
+ expect(uri).to receive(:open).and_return(temp_file)
54
+ subject = described_class.new credentials: credentials
55
+ expect { subject.fetch('path') {} }.to_not raise_error
56
+ end
57
+
58
+ it 'Passes extra options on' do
59
+ expect(URI).to receive(:parse).and_return(uri)
60
+ expect(uri).to receive(:open)
61
+ .with({ a: 'a', http_basic_authentication: [] })
62
+ .and_return(temp_file)
63
+ subject.fetch('path', { a: 'a' }) {}
64
+ end
65
+ end
66
+
67
+ describe '#fetch_unzipped' do
68
+ it 'Returns what Zlib::GzipReader.open does' do
69
+ reader = double Zlib::GzipReader
70
+ expect(subject).to receive(:fetch).with('url').and_yield(temp_file)
71
+ expect(Zlib::GzipReader).to receive(:new).with(temp_file).and_return(reader)
72
+ expect { |a| subject.fetch_unzipped('url', &a) }.to yield_with_args(reader)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NationalRail::Credentials do
4
+ it 'Is a RailFeeds::Credentials' do
5
+ expect(described_class).to be < RailFeeds::Credentials
6
+ end
7
+
8
+ it 'Has seperate values to RailFeeds::Credentials' do
9
+ described_class.configure username: 'a', password: 'b'
10
+ expect(described_class.username).to_not eq RailFeeds::Credentials.username
11
+ expect(described_class.password).to_not eq RailFeeds::Credentials.password
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RailFeeds::NationalRail::HTTPClient do
4
+ let(:uri) { double URI }
5
+
6
+ it 'Is a RailFeeds::HTTPClient' do
7
+ expect(described_class).to be < RailFeeds::HTTPClient
8
+ end
9
+
10
+ it 'has correct default credentials' do
11
+ expect(subject.send(:credentials)).to eq RailFeeds::NationalRail::Credentials
12
+ end
13
+
14
+ describe '#fetch' do
15
+ context 'Gets an auth token when' do
16
+ before(:each) { allow(uri).to receive(:open) }
17
+
18
+ it 'It has not been fetched yet' do
19
+ expect(subject).to receive(:auth_token)
20
+ expect(URI).to receive(:parse).with('https://datafeeds.nationalrail.co.uk/path').and_return(uri)
21
+ subject.fetch('path') {}
22
+ end
23
+
24
+ it 'Token is over an hour old' do
25
+ expect(subject).to receive(:auth_token).twice
26
+ expect(URI).to receive(:parse).with('https://datafeeds.nationalrail.co.uk/path')
27
+ .and_return(uri).twice
28
+ subject.fetch('path') {} # Get the auth token
29
+ Timecop.travel 3601
30
+ subject.fetch('path') {} # Token expired so should reget it
31
+ end
32
+
33
+ it 'Getting the token' do
34
+ subject = described_class.new(credentials: RailFeeds::Credentials.new(username: '', password: ''))
35
+ response = double Net::HTTPCreated
36
+ expect(response).to receive(:value)
37
+ expect(response).to receive(:body).and_return('{"token":"TOKEN"}')
38
+ expect(Net::HTTP).to receive(:post_form)
39
+ .with(URI('https://datafeeds.nationalrail.co.uk/authenticate'), { username: '', password: '' })
40
+ .and_return(response)
41
+ expect(subject.send(:auth_token)).to eq 'TOKEN'
42
+ end
43
+ end
44
+
45
+ context 'Has a valid auth_token' do
46
+ before :each do
47
+ allow(subject).to receive(:auth_token).and_return('auth_token')
48
+ end
49
+
50
+ it 'Adds server to path then delegates to super' do
51
+ expect(URI).to receive(:parse).with('https://datafeeds.nationalrail.co.uk/path').and_return(uri)
52
+ expect(uri).to receive(:open)
53
+ subject.fetch('path') {}
54
+ end
55
+ end
56
+ end
57
+ end