aptible-resource 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a3754a5511515adb9046e4880d742c872779fb6d
4
- data.tar.gz: 113ad8d809c64b1b7cfdafd5c522ad598f161d37
3
+ metadata.gz: dded993c5744f3cdda2e692ac1ad19dc5d45a334
4
+ data.tar.gz: d2351212650888e95d0ae6847a0d02cd82f4eaad
5
5
  SHA512:
6
- metadata.gz: a3e134dc2dcbec98c6717577eb8e9500b3d0aacfd53921b21d0703f76340217951dfe54e30ac53c5e30e7a32b561e874b1743b38383eb702b9857a071c154f74
7
- data.tar.gz: 3a30508f4289cfe2075e0852fee14f529cc9211e2723e7617c13e9f85f8c7eb226dbf29246146c3003c0865b1fad79adf9c0d94e36af8575dee8f4e95f9dc141
6
+ metadata.gz: 4a0e35ad0dd2a1e845edbcd42e74c7242ad1be592dc78fb61f8f102ead28eeb279aa435cb2fb2508a06c482a228e7717a78ef9c2c1d24bff427f4da2b76f2c39
7
+ data.tar.gz: 3e65e843ce94ba3a907683639c4b9dad74a8f8926cb14fb4cf536405d5fe48c3a968494e485a587ecbf5f55f875a4143235ec05da86351645cc7ae15dbf824dc
@@ -33,5 +33,5 @@ Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency 'rake'
34
34
  spec.add_development_dependency 'rspec', '~> 2.0'
35
35
  spec.add_development_dependency 'pry'
36
- spec.add_development_dependency 'webmock'
36
+ spec.add_development_dependency 'webmock', '~> 2.3.2'
37
37
  end
@@ -1,12 +1,15 @@
1
1
  require 'aptible/resource/version'
2
2
  require 'aptible/resource/base'
3
3
  require 'aptible/resource/default_retry_coordinator'
4
+ require 'aptible/resource/null_retry_coordinator'
4
5
  require 'gem_config'
5
6
 
6
7
  module Aptible
7
8
  module Resource
8
9
  include GemConfig::Base
9
10
 
11
+ RETRY_COORDINATOR_OVERRIDE = :override_retry_coordinator_class
12
+
10
13
  with_configuration do
11
14
  has :retry_coordinator_class,
12
15
  classes: [Class],
@@ -16,5 +19,26 @@ module Aptible
16
19
  classes: [String],
17
20
  default: "aptible-resource #{Aptible::Resource::VERSION}"
18
21
  end
22
+
23
+ class << self
24
+ def without_retry(&block)
25
+ override_retry_coordinator_class(
26
+ Aptible::Resource::NullRetryCoordinator, &block
27
+ )
28
+ end
29
+
30
+ def override_retry_coordinator_class(klass)
31
+ Thread.current[RETRY_COORDINATOR_OVERRIDE] = klass
32
+ yield if block_given?
33
+ ensure
34
+ Thread.current[RETRY_COORDINATOR_OVERRIDE] = nil
35
+ end
36
+
37
+ def retry_coordinator_class
38
+ override = Thread.current[RETRY_COORDINATOR_OVERRIDE]
39
+ return override if override
40
+ configuration.retry_coordinator_class
41
+ end
42
+ end
19
43
  end
20
44
  end
@@ -12,9 +12,6 @@ require 'aptible/resource/adapter'
12
12
  require 'aptible/resource/errors'
13
13
  require 'aptible/resource/boolean'
14
14
 
15
- # Open errors that make sense
16
- require 'aptible/resource/ext/faraday'
17
-
18
15
  module Aptible
19
16
  module Resource
20
17
  # rubocop:disable ClassLength
@@ -289,6 +286,11 @@ module Aptible
289
286
 
290
287
  def delete
291
288
  super
289
+ rescue HyperResource::ServerError
290
+ raise
291
+ rescue HyperResource::ClientError => e
292
+ # Already deleted
293
+ raise unless e.response.status == 404
292
294
  rescue HyperResource::ResponseError
293
295
  # HyperResource/Faraday choke on empty response bodies
294
296
  nil
@@ -1,14 +1,36 @@
1
1
  module Aptible
2
2
  module Resource
3
3
  class DefaultRetryCoordinator
4
- attr_reader :resource
4
+ attr_reader :resource, :retry_schedule
5
+
6
+ IDEMPOTENT_METHODS = [
7
+ # Idempotent as per RFC
8
+ :delete, :get, :head, :options, :put,
9
+ # Idempotent on our APIs
10
+ :patch
11
+ ].freeze
12
+ RETRY_ERRORS = [Faraday::Error, HyperResource::ServerError].freeze
5
13
 
6
14
  def initialize(resource)
7
15
  @resource = resource
16
+ @retry_schedule = new_retry_schedule
17
+ end
18
+
19
+ def retry?(method, err)
20
+ # rubocop:disable Style/CaseEquality
21
+ return false unless RETRY_ERRORS.any? { |c| c === err }
22
+ return false unless IDEMPOTENT_METHODS.include?(method)
23
+ retry_in = retry_schedule.shift
24
+ return false if retry_in.nil?
25
+ sleep retry_in
26
+ true
27
+ # rubocop:enable Style/CaseEquality
8
28
  end
9
29
 
10
- def retry?(_error)
11
- false
30
+ private
31
+
32
+ def new_retry_schedule
33
+ [0.2, 0.8, 2]
12
34
  end
13
35
  end
14
36
  end
@@ -0,0 +1,12 @@
1
+ module Aptible
2
+ module Resource
3
+ class NullRetryCoordinator
4
+ def initialize(_)
5
+ end
6
+
7
+ def retry?(_, _)
8
+ false
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module Resource
3
- VERSION = '0.3.8'.freeze
3
+ VERSION = '0.4.0'.freeze
4
4
  end
5
5
  end
@@ -10,6 +10,7 @@ require 'hyper_resource/adapter'
10
10
  require 'hyper_resource/adapter/hal_json'
11
11
 
12
12
  require 'hyper_resource/modules/http'
13
+ require 'hyper_resource/modules/http/wrap_errors'
13
14
  require 'hyper_resource/modules/internal_attributes'
14
15
 
15
16
  require 'rubygems' if RUBY_VERSION[0..2] == '1.8'
@@ -59,6 +59,7 @@ class HyperResource
59
59
  ## Returns a hash of the attributes and values which have been changed
60
60
  ## since creation time.
61
61
  def changed_attributes
62
+ return {} if @_hr_changed.nil?
62
63
  @_hr_changed.select{|k,v| v}.keys.inject({}) {|h,k| h[k]=self[k]; h}
63
64
  end
64
65
 
@@ -27,7 +27,7 @@ class HyperResource
27
27
  message = "#{message} (#{error})"
28
28
  end
29
29
  elsif self.response
30
- message = "#{message} (\"#{self.response.inspect}\")"
30
+ message = "#{message} (#{response.body})"
31
31
  end
32
32
 
33
33
  super(message, attrs)
@@ -78,8 +78,8 @@ class HyperResource
78
78
  builder.basic_auth(*ba)
79
79
  end
80
80
 
81
+ builder.use WrapErrors # This has to be first!
81
82
  builder.request :url_encoded
82
- builder.request :retry
83
83
  builder.adapter Faraday.default_adapter
84
84
  end
85
85
  end
@@ -88,32 +88,35 @@ class HyperResource
88
88
 
89
89
  def execute_request
90
90
  raise 'execute_request needs a block!' unless block_given?
91
- retry_coordinator = Aptible::Resource.configuration
92
- .retry_coordinator_class.new(self)
91
+ retry_coordinator = Aptible::Resource.retry_coordinator_class.new(self)
93
92
 
94
93
  n_retry = 0
94
+
95
95
  begin
96
- finish_up(yield)
97
- rescue HyperResource::ResponseError => e
96
+ begin
97
+ finish_up(yield)
98
+ rescue HyperResource::ResponseError => e
99
+ raise WrapErrors::WrappedError.new(e.response.env.method, e)
100
+ end
101
+ rescue WrapErrors::WrappedError => e
98
102
  n_retry += 1
99
- raise e if n_retry > MAX_COORDINATOR_RETRIES
100
- retry if retry_coordinator.retry?(e)
101
- raise e
103
+ raise e.err if n_retry > MAX_COORDINATOR_RETRIES
104
+ retry if retry_coordinator.retry?(e.method, e.err)
105
+ raise e.err
102
106
  end
103
107
  end
104
108
 
105
109
  def finish_up(response)
110
+ body = adapter_error = nil
111
+
106
112
  begin
107
113
  body = adapter.deserialize(response.body) unless response.body.nil?
108
114
  rescue StandardError => e
109
- raise HyperResource::ResponseError.new(
110
- 'Error when deserializing response body',
111
- response: response,
112
- cause: e
113
- )
115
+ adapter_error = e
114
116
  end
115
117
 
116
118
  status = response.status
119
+
117
120
  if status / 100 == 2
118
121
  elsif status / 100 == 3
119
122
  raise 'HyperResource does not handle redirects'
@@ -133,6 +136,14 @@ class HyperResource
133
136
 
134
137
  end
135
138
 
139
+ if adapter_error
140
+ raise HyperResource::ResponseError.new(
141
+ 'Error when deserializing response body',
142
+ response: response,
143
+ cause: e
144
+ )
145
+ end
146
+
136
147
  # Unfortunately, HyperResource insists on having response and body
137
148
  # be attributes..
138
149
  self.response = response
@@ -0,0 +1,22 @@
1
+ class HyperResource
2
+ module Modules
3
+ module HTTP
4
+ class WrapErrors < Faraday::Middleware
5
+ class WrappedError < StandardError
6
+ attr_reader :method, :err
7
+
8
+ def initialize(method, err)
9
+ @method = method
10
+ @err = err
11
+ end
12
+ end
13
+
14
+ def call(env)
15
+ @app.call(env)
16
+ rescue StandardError => e
17
+ raise WrappedError.new(env.method, e)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -32,7 +32,7 @@ describe Aptible::Resource::Base do
32
32
  end
33
33
 
34
34
  [Api, Api::Mainframe].each do |klass|
35
- allow_any_instance_of(klass).to receive(:find_by_url) do |u, _|
35
+ allow_any_instance_of(klass).to receive(:find_by_url) do |_, u, _|
36
36
  calls << u
37
37
  page = pages[u]
38
38
  raise "Accessed unexpected URL #{u}" if page.nil?
@@ -267,6 +267,18 @@ describe Aptible::Resource::Base do
267
267
  end
268
268
  end
269
269
 
270
+ describe '#delete' do
271
+ it 'allows an empty response' do
272
+ stub_request(:delete, subject.root_url).to_return(body: '', status: 200)
273
+ expect(subject.delete).to be_nil
274
+ end
275
+
276
+ it 'ignores 404s' do
277
+ stub_request(:delete, subject.root_url).to_return(body: '', status: 404)
278
+ expect(subject.delete).to be_nil
279
+ end
280
+ end
281
+
270
282
  context '.has_many' do
271
283
  let(:mainframe) { Api::Mainframe.new }
272
284
  let(:mainframes_link) { HyperResource::Link.new(href: '/mainframes') }
@@ -401,7 +413,7 @@ describe Aptible::Resource::Base do
401
413
 
402
414
  context 'retry_coordinator_class' do
403
415
  it 'should not retry if the proc returns false' do
404
- configure_new_coordinator { define_method(:retry?) { |_e| false } }
416
+ configure_new_coordinator { define_method(:retry?) { |_, _e| false } }
405
417
 
406
418
  stub_request(:get, 'foo.com')
407
419
  .to_return(body: { error: 'foo' }.to_json, status: 401).then
@@ -412,7 +424,7 @@ describe Aptible::Resource::Base do
412
424
  end
413
425
 
414
426
  it 'should retry if the proc returns true' do
415
- configure_new_coordinator { define_method(:retry?) { |_e| true } }
427
+ configure_new_coordinator { define_method(:retry?) { |_, _e| true } }
416
428
 
417
429
  stub_request(:get, 'foo.com')
418
430
  .to_return(body: { error: 'foo' }.to_json, status: 401).then
@@ -426,7 +438,7 @@ describe Aptible::Resource::Base do
426
438
  failures = 0
427
439
 
428
440
  configure_new_coordinator do
429
- define_method(:retry?) { |_e| failures += 1 || true }
441
+ define_method(:retry?) { |_, _e| failures += 1 || true }
430
442
  end
431
443
 
432
444
  stub_request(:get, 'foo.com')
@@ -454,7 +466,7 @@ describe Aptible::Resource::Base do
454
466
 
455
467
  configure_new_coordinator do
456
468
  define_method(:initialize) { |r| resource = r }
457
- define_method(:retry?) { |e| (exception = e) && false }
469
+ define_method(:retry?) { |_, e| (exception = e) && false }
458
470
  end
459
471
 
460
472
  stub_request(:get, 'foo.com')
@@ -472,7 +484,7 @@ describe Aptible::Resource::Base do
472
484
  retry_was_called = false
473
485
 
474
486
  configure_new_coordinator do
475
- define_method(:retry?) do |_e|
487
+ define_method(:retry?) do |_, _e|
476
488
  resource.token = 'bar'
477
489
  # resource.headers['Authorization'] = 'Bearer bar'
478
490
  retry_was_called = true
@@ -495,7 +507,7 @@ describe Aptible::Resource::Base do
495
507
  n = 0
496
508
 
497
509
  configure_new_coordinator do
498
- define_method(:retry?) { |_e| n += 1 || true }
510
+ define_method(:retry?) { |_, _e| n += 1 || true }
499
511
  end
500
512
 
501
513
  stub_request(:get, 'foo.com')
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+
3
+ # With webmock (fake connections), to check how we handle timeouts.
4
+ describe Aptible::Resource::Base do
5
+ let(:body) do
6
+ { 'hello' => '1', '_links' => { 'self' => { 'href' => href } } }
7
+ end
8
+ let(:json_body) { JSON.unparse(body) }
9
+ let(:href) { 'https://resource.example.com/mainframes/1' }
10
+
11
+ subject do
12
+ stub = stub_request(:get, href).to_return(body: json_body)
13
+ begin
14
+ Api::Mainframe.find(1)
15
+ ensure
16
+ remove_request_stub(stub)
17
+ end
18
+ end
19
+
20
+ let(:sleeps) { [] }
21
+
22
+ def time_slept
23
+ sleeps.sum
24
+ end
25
+
26
+ before do
27
+ allow_any_instance_of(Aptible::Resource::DefaultRetryCoordinator)
28
+ .to receive(:sleep) { |_, t| sleeps << t }
29
+ end
30
+
31
+ context 'server errors' do
32
+ shared_examples 'retry examples' do |method|
33
+ context "#{method.to_s.upcase} requests" do
34
+ it 'should retry a server error' do
35
+ stub_request(method, href)
36
+ .to_return(body: { error: 'foo' }.to_json, status: 500).then
37
+ .to_return(body: json_body)
38
+
39
+ expect(subject.public_send(method).body).to eq(body)
40
+ end
41
+
42
+ it 'should retry a server error with no body' do
43
+ stub_request(method, href)
44
+ .to_return(body: '', status: 502).then
45
+ .to_return(body: json_body)
46
+
47
+ expect(subject.public_send(method).body).to eq(body)
48
+ end
49
+
50
+ it 'should eventually give up' do
51
+ stub_request(method, href)
52
+ .to_return(body: { error: 'foo' }.to_json, status: 500)
53
+
54
+ expect { subject.public_send(method) }
55
+ .to raise_error(HyperResource::ServerError)
56
+ end
57
+
58
+ it 'should not retry a client error' do
59
+ stub_request(method, href)
60
+ .to_return(body: { error: 'foo' }.to_json, status: 400).then
61
+ .to_return(body: json_body)
62
+
63
+ expect { subject.public_send(method) }
64
+ .to raise_error(HyperResource::ClientError)
65
+ end
66
+
67
+ it 'should not retry parse errors' do
68
+ stub_request(method, href)
69
+ .to_return(body: 'boo', status: 400).then
70
+ .to_return(body: json_body)
71
+
72
+ expect { subject.public_send(method) }
73
+ .to raise_error(HyperResource::ResponseError)
74
+ end
75
+ end
76
+ end
77
+
78
+ include_examples 'retry examples', :delete
79
+ include_examples 'retry examples', :get
80
+ include_examples 'retry examples', :put
81
+ include_examples 'retry examples', :patch
82
+
83
+ context 'POST requests' do
84
+ it 'should not retry a server error' do
85
+ stub_request(:post, href)
86
+ .to_return(body: { error: 'foo' }.to_json, status: 504).then
87
+ .to_return(body: json_body)
88
+
89
+ expect { subject.post }
90
+ .to raise_error(HyperResource::ServerError)
91
+
92
+ expect(time_slept).to eq(0)
93
+ end
94
+ end
95
+
96
+ context 'retry coordinator overrides' do
97
+ before do
98
+ stub_request(:get, href)
99
+ .to_return(body: { error: 'foo' }.to_json, status: 500).then
100
+ .to_return(body: { status: 'ok' }.to_json, status: 200)
101
+ end
102
+
103
+ it 'should be overridden by override_retry_coordinator_class ' do
104
+ expect do
105
+ klass = Aptible::Resource::NullRetryCoordinator
106
+ Aptible::Resource.override_retry_coordinator_class(klass) do
107
+ subject.get
108
+ end
109
+ end.to raise_error(HyperResource::ServerError)
110
+ end
111
+
112
+ it 'should disable retries with override_retry_coordinator_class' do
113
+ expect { Aptible::Resource.without_retry { subject.get } }
114
+ .to raise_error(HyperResource::ServerError)
115
+ end
116
+ end
117
+ end
118
+
119
+ context 'network errors' do
120
+ context 'with mock connections' do
121
+ it 'should retry timeout errors' do
122
+ stub_request(:get, href)
123
+ .to_timeout.then
124
+ .to_timeout.then
125
+ .to_return(body: json_body)
126
+
127
+ expect(subject.get.body).to eq(body)
128
+ end
129
+
130
+ it 'should retry timeout errors (Errno::ETIMEDOUT)' do
131
+ stub_request(:get, href)
132
+ .to_raise(Errno::ETIMEDOUT).then
133
+ .to_raise(Errno::ETIMEDOUT).then
134
+ .to_return(body: json_body)
135
+
136
+ expect(subject.get.body).to eq(body)
137
+ end
138
+
139
+ it 'should retry timeout errors (Net::OpenTimeout)' do
140
+ stub_request(:get, href)
141
+ .to_raise(Net::OpenTimeout).then
142
+ .to_raise(Net::OpenTimeout).then
143
+ .to_return(body: json_body)
144
+
145
+ expect(subject.get.body).to eq(body)
146
+ end
147
+
148
+ it 'should retry connection errors' do
149
+ stub_request(:get, href)
150
+ .to_raise(Errno::ECONNREFUSED).then
151
+ .to_raise(Errno::ECONNREFUSED).then
152
+ .to_return(body: json_body)
153
+
154
+ expect(subject.get.body).to eq(body)
155
+ end
156
+
157
+ it 'should not retry POSTs' do
158
+ stub_request(:post, href)
159
+ .to_timeout.then
160
+ .to_return(body: json_body)
161
+
162
+ expect { subject.post }.to raise_error(Faraday::ConnectionFailed)
163
+ end
164
+ end
165
+
166
+ context 'without connections' do
167
+ around do |example|
168
+ WebMock.allow_net_connect!
169
+ example.run
170
+ WebMock.disable_net_connect!
171
+ end
172
+
173
+ it 'default to 10 seconds of timeout and retries 4 times' do
174
+ # This really relies on how exactly MRI implements Net::HTTP open
175
+ # timeouts
176
+ skip 'MRI implementation-specific' if RUBY_PLATFORM == 'java'
177
+
178
+ expect(Timeout).to receive(:timeout)
179
+ .with(10, Net::OpenTimeout)
180
+ .exactly(4).times
181
+ .and_raise(Net::OpenTimeout)
182
+
183
+ expect { subject.get }.to raise_error(Faraday::ConnectionFailed)
184
+ expect(sleeps.size).to eq(3)
185
+ end
186
+ end
187
+ end
188
+ end
data/spec/spec_helper.rb CHANGED
@@ -20,4 +20,8 @@ WebMock.disable_net_connect!
20
20
  RSpec.configure do |config|
21
21
  config.before { Aptible::Resource.configuration.reset }
22
22
  config.before { WebMock.reset! }
23
+
24
+ config.mock_with :rspec do |mocks|
25
+ mocks.yield_receiver_to_any_instance_implementation_blocks = true
26
+ end
23
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aptible-resource
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank Macreery
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-28 00:00:00.000000000 Z
11
+ date: 2017-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uri_template
@@ -174,16 +174,16 @@ dependencies:
174
174
  name: webmock
175
175
  requirement: !ruby/object:Gem::Requirement
176
176
  requirements:
177
- - - ">="
177
+ - - "~>"
178
178
  - !ruby/object:Gem::Version
179
- version: '0'
179
+ version: 2.3.2
180
180
  type: :development
181
181
  prerelease: false
182
182
  version_requirements: !ruby/object:Gem::Requirement
183
183
  requirements:
184
- - - ">="
184
+ - - "~>"
185
185
  - !ruby/object:Gem::Version
186
- version: '0'
186
+ version: 2.3.2
187
187
  description: Foundation classes for Aptible resource server gems
188
188
  email:
189
189
  - frank@macreery.com
@@ -206,7 +206,7 @@ files:
206
206
  - lib/aptible/resource/boolean.rb
207
207
  - lib/aptible/resource/default_retry_coordinator.rb
208
208
  - lib/aptible/resource/errors.rb
209
- - lib/aptible/resource/ext/faraday.rb
209
+ - lib/aptible/resource/null_retry_coordinator.rb
210
210
  - lib/aptible/resource/version.rb
211
211
  - lib/hyper_resource.rb
212
212
  - lib/hyper_resource/adapter.rb
@@ -216,12 +216,13 @@ files:
216
216
  - lib/hyper_resource/link.rb
217
217
  - lib/hyper_resource/links.rb
218
218
  - lib/hyper_resource/modules/http.rb
219
+ - lib/hyper_resource/modules/http/wrap_errors.rb
219
220
  - lib/hyper_resource/modules/internal_attributes.rb
220
221
  - lib/hyper_resource/objects.rb
221
222
  - lib/hyper_resource/response.rb
222
223
  - lib/hyper_resource/version.rb
223
224
  - spec/aptible/resource/base_spec.rb
224
- - spec/aptible/resource/network_spec.rb
225
+ - spec/aptible/resource/retry_spec.rb
225
226
  - spec/fixtures/api.rb
226
227
  - spec/fixtures/mainframe.rb
227
228
  - spec/fixtures/token.rb
@@ -252,7 +253,7 @@ specification_version: 4
252
253
  summary: Foundation classes for Aptible resource server gems
253
254
  test_files:
254
255
  - spec/aptible/resource/base_spec.rb
255
- - spec/aptible/resource/network_spec.rb
256
+ - spec/aptible/resource/retry_spec.rb
256
257
  - spec/fixtures/api.rb
257
258
  - spec/fixtures/mainframe.rb
258
259
  - spec/fixtures/token.rb
@@ -1,5 +0,0 @@
1
- Faraday::Adapter::NetHttp.class_eval do |cls|
2
- # Work around https://github.com/lostisland/faraday/issues/561 by treating
3
- # connection timeouts as... timeouts.
4
- cls::NET_HTTP_EXCEPTIONS.delete Net::OpenTimeout if defined?(Net::OpenTimeout)
5
- end
@@ -1,49 +0,0 @@
1
- require 'spec_helper'
2
-
3
- # With webmock (fake connections), to check how we handle timeouts.
4
- describe Aptible::Resource::Base do
5
- let(:body) { { 'hello' => '1' } }
6
- let(:json_body) { JSON.unparse(body) }
7
- let(:domain) { 'api.aptible.com' }
8
-
9
- subject { Api.new(root: "http://#{domain}") }
10
-
11
- context 'with mock connections' do
12
- it 'should retry timeout errors' do
13
- stub_request(:get, domain)
14
- .to_timeout.then
15
- .to_timeout.then
16
- .to_return(body: json_body)
17
-
18
- expect(subject.get.body).to eq(body)
19
- end
20
-
21
- it 'should not retry POSTs' do
22
- stub_request(:post, domain)
23
- .to_timeout.then
24
- .to_return(body: json_body)
25
-
26
- expect { subject.post }.to raise_error(Faraday::TimeoutError)
27
- end
28
- end
29
-
30
- context 'without connections' do
31
- around do |example|
32
- WebMock.allow_net_connect!
33
- example.run
34
- WebMock.disable_net_connect!
35
- end
36
-
37
- it 'default to 10 seconds of timeout and retry 3 times' do
38
- # This really relies on how exactly MRI implements Net::HTTP open timeouts
39
- skip 'MRI implementation-specific' if RUBY_PLATFORM == 'java'
40
-
41
- expect(Timeout).to receive(:timeout)
42
- .with(10, Net::OpenTimeout)
43
- .exactly(3).times
44
- .and_raise(Net::OpenTimeout)
45
-
46
- expect { subject.all }.to raise_error(Faraday::Error::TimeoutError)
47
- end
48
- end
49
- end