aptible-resource 0.3.8 → 0.4.0

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