rail_feeds 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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