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 +4 -4
- data/aptible-resource.gemspec +1 -1
- data/lib/aptible/resource.rb +24 -0
- data/lib/aptible/resource/base.rb +5 -3
- data/lib/aptible/resource/default_retry_coordinator.rb +25 -3
- data/lib/aptible/resource/null_retry_coordinator.rb +12 -0
- data/lib/aptible/resource/version.rb +1 -1
- data/lib/hyper_resource.rb +1 -0
- data/lib/hyper_resource/attributes.rb +1 -0
- data/lib/hyper_resource/exceptions.rb +1 -1
- data/lib/hyper_resource/modules/http.rb +24 -13
- data/lib/hyper_resource/modules/http/wrap_errors.rb +22 -0
- data/spec/aptible/resource/base_spec.rb +19 -7
- data/spec/aptible/resource/retry_spec.rb +188 -0
- data/spec/spec_helper.rb +4 -0
- metadata +10 -9
- data/lib/aptible/resource/ext/faraday.rb +0 -5
- data/spec/aptible/resource/network_spec.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dded993c5744f3cdda2e692ac1ad19dc5d45a334
|
4
|
+
data.tar.gz: d2351212650888e95d0ae6847a0d02cd82f4eaad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a0e35ad0dd2a1e845edbcd42e74c7242ad1be592dc78fb61f8f102ead28eeb279aa435cb2fb2508a06c482a228e7717a78ef9c2c1d24bff427f4da2b76f2c39
|
7
|
+
data.tar.gz: 3e65e843ce94ba3a907683639c4b9dad74a8f8926cb14fb4cf536405d5fe48c3a968494e485a587ecbf5f55f875a4143235ec05da86351645cc7ae15dbf824dc
|
data/aptible-resource.gemspec
CHANGED
@@ -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
|
data/lib/aptible/resource.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
30
|
+
private
|
31
|
+
|
32
|
+
def new_retry_schedule
|
33
|
+
[0.2, 0.8, 2]
|
12
34
|
end
|
13
35
|
end
|
14
36
|
end
|
data/lib/hyper_resource.rb
CHANGED
@@ -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
|
|
@@ -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.
|
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
|
-
|
97
|
-
|
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
|
-
|
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.
|
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:
|
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:
|
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:
|
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/
|
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/
|
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/
|
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,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
|