aptible-resource 0.3.6 → 0.3.7

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: 31f491541f4e8330accd97a53c1896fe7fca26e2
4
- data.tar.gz: fa2f72d22a0736be3d38d61d4731e7582ef64c78
3
+ metadata.gz: 077c3b938aed411e3c460ba3e0fe7f01ccf1cd52
4
+ data.tar.gz: 66a968ab57d1f73170cc960f9f547e9fd231dc2d
5
5
  SHA512:
6
- metadata.gz: ea9ed96d16623b924cc4bbd463e128bab5d8ce1463a10451faa3de64be1b079c88aec56efad7ad079b7fea3877cd906c9bae5b24b2048d08ad99bdfa5f23a4a0
7
- data.tar.gz: 406299c0d2a4cff3faca28f86c736374a27f38cde09ea6d050a127f74853e6d5916477d4bf03cc5feb228a4ee9cc7c8151151dbace303c8f44fab0a847c8333b
6
+ metadata.gz: 39b3ecc5f467482ea95b45f6408bb2c0e10fb8a0050ad249118d1604c9819d0349ea09c4662e18731a9e3598c2e6f7cd177049a8e9191a2ddc5fc23bd499eb31
7
+ data.tar.gz: 8fc0837539473ea9c07e6892f0b5748270c93ecf805d1b2ee33e54bc2eaec6572df337f5a00a86e0031f822d49a4baf5db3cf714f7747259392f6ac0cd2965c7
@@ -25,7 +25,8 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency 'json'
26
26
 
27
27
  spec.add_dependency 'fridge'
28
- spec.add_dependency 'activesupport'
28
+ spec.add_dependency 'activesupport', '~> 4.0'
29
+ spec.add_dependency 'gem_config', '~> 0.3.1'
29
30
 
30
31
  spec.add_development_dependency 'bundler', '~> 1.3'
31
32
  spec.add_development_dependency 'aptible-tasks'
@@ -1,7 +1,20 @@
1
1
  require 'aptible/resource/version'
2
2
  require 'aptible/resource/base'
3
+ require 'aptible/resource/default_retry_coordinator'
4
+ require 'gem_config'
3
5
 
4
6
  module Aptible
5
7
  module Resource
8
+ include GemConfig::Base
9
+
10
+ with_configuration do
11
+ has :retry_coordinator_class,
12
+ classes: [Class],
13
+ default: DefaultRetryCoordinator
14
+
15
+ has :user_agent,
16
+ classes: [String],
17
+ default: "aptible-resource #{Aptible::Resource::VERSION}"
18
+ end
6
19
  end
7
20
  end
@@ -19,7 +19,8 @@ module Aptible
19
19
  module Resource
20
20
  # rubocop:disable ClassLength
21
21
  class Base < HyperResource
22
- attr_accessor :token, :errors
22
+ attr_accessor :errors
23
+ attr_reader :token
23
24
 
24
25
  def self.get_data_type_from_response(response)
25
26
  return nil unless response && response.body
@@ -79,11 +80,8 @@ module Aptible
79
80
  # REVIEW: Should exception be raised if return type mismatch?
80
81
  new(options).find_by_url(url)
81
82
  rescue HyperResource::ClientError => e
82
- if e.response.status == 404
83
- return nil
84
- else
85
- raise e
86
- end
83
+ return nil if e.response.status == 404
84
+ raise e
87
85
  end
88
86
 
89
87
  def self.create!(params = {})
@@ -146,6 +144,7 @@ module Aptible
146
144
  memoized
147
145
  elsif links[relation]
148
146
  depaginated = self.class.all(href: links[relation].base_href,
147
+ token: token,
149
148
  headers: headers)
150
149
  instance_variable_set("@#{relation}", depaginated)
151
150
  end
@@ -154,6 +153,7 @@ module Aptible
154
153
  define_method "each_#{relation.to_s.singularize}" do |&block|
155
154
  return if block.nil?
156
155
  self.class.each_page(href: links[relation].base_href,
156
+ token: token,
157
157
  headers: headers) do |page|
158
158
  page.each { |entry| block.call entry }
159
159
  end
@@ -225,12 +225,11 @@ module Aptible
225
225
  end
226
226
 
227
227
  def initialize(options = {})
228
- if options.is_a?(Hash)
229
- self.token = options[:token]
230
- populate_default_options!(options)
231
- end
228
+ return super(options) unless options.is_a?(Hash)
232
229
 
230
+ populate_default_options!(options)
233
231
  super(options)
232
+ self.token = options[:token] if options[:token]
234
233
  end
235
234
 
236
235
  def populate_default_options!(options)
@@ -238,8 +237,11 @@ module Aptible
238
237
  options[:namespace] ||= namespace
239
238
  options[:headers] ||= {}
240
239
  options[:headers]['Content-Type'] = 'application/json'
241
- return unless options[:token]
242
- options[:headers]['Authorization'] = "Bearer #{bearer_token}"
240
+ end
241
+
242
+ def token=(val)
243
+ @token = val
244
+ headers['Authorization'] = "Bearer #{bearer_token}"
243
245
  end
244
246
 
245
247
  def adapter
@@ -247,11 +249,11 @@ module Aptible
247
249
  end
248
250
 
249
251
  def namespace
250
- fail 'Resource server namespace must be defined by subclass'
252
+ raise 'Resource server namespace must be defined by subclass'
251
253
  end
252
254
 
253
255
  def root_url
254
- fail 'Resource server root URL must be defined by subclass'
256
+ raise 'Resource server root URL must be defined by subclass'
255
257
  end
256
258
 
257
259
  def find_by_url(url_or_href)
@@ -268,7 +270,10 @@ module Aptible
268
270
  end
269
271
  end
270
272
 
273
+ # rubocop:disable Style/Alias
271
274
  alias_method :_hyperresource_update, :update
275
+ # rubocop:enable Style/Alias
276
+
272
277
  def update!(params)
273
278
  _hyperresource_update(self.class.normalize_params(params))
274
279
  rescue HyperResource::ResponseError => e
@@ -288,11 +293,14 @@ module Aptible
288
293
  # HyperResource/Faraday choke on empty response bodies
289
294
  nil
290
295
  end
296
+
297
+ # rubocop:disable Style/Alias
291
298
  alias_method :destroy, :delete
299
+ # rubocop:enable Style/Alias
292
300
 
293
301
  # NOTE: The following does not update the object in-place
294
302
  def reload
295
- self.class.find_by_url(href, headers: headers)
303
+ self.class.find_by_url(href, token: token, headers: headers)
296
304
  end
297
305
 
298
306
  def errors
@@ -0,0 +1,15 @@
1
+ module Aptible
2
+ module Resource
3
+ class DefaultRetryCoordinator
4
+ attr_reader :resource
5
+
6
+ def initialize(resource)
7
+ @resource = resource
8
+ end
9
+
10
+ def retry?(_error)
11
+ false
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module Resource
3
- VERSION = '0.3.6'
3
+ VERSION = '0.3.7'.freeze
4
4
  end
5
5
  end
@@ -199,6 +199,7 @@ public
199
199
  :headers => self.headers,
200
200
  :namespace => self.namespace,
201
201
  :faraday_options => self.faraday_options,
202
+ :token => self.token,
202
203
  :href => href)
203
204
  end
204
205
 
@@ -48,6 +48,7 @@ class HyperResource
48
48
  if collection.is_a? Hash
49
49
  r = rc.new(:root => rsrc.root,
50
50
  :headers => rsrc.headers,
51
+ :token => rsrc.token,
51
52
  :namespace => rsrc.namespace)
52
53
  r.body = collection
53
54
  r = classify(collection, r)
@@ -56,6 +57,7 @@ class HyperResource
56
57
  objs[name] = collection.map do |obj|
57
58
  r = rc.new(:root => rsrc.root,
58
59
  :headers => rsrc.headers,
60
+ :token => rsrc.token,
59
61
  :namespace => rsrc.namespace)
60
62
  r.body = obj
61
63
  r = classify(obj, r)
@@ -73,8 +75,10 @@ class HyperResource
73
75
  klass = rsrc.class.namespaced_class(type_name, namespace)
74
76
 
75
77
  if klass
78
+ # TODO: Why does this not use klass.new(rsrc)?
76
79
  rsrc = klass.new(:root => rsrc.root,
77
80
  :headers => rsrc.headers,
81
+ :token => rsrc.token,
78
82
  :namespace => rsrc.namespace)
79
83
  rsrc.body = resp
80
84
  end
@@ -6,13 +6,16 @@ require 'digest/md5'
6
6
  class HyperResource
7
7
  module Modules
8
8
  module HTTP
9
+ # A (high) limit to the number of retries a coordinator can ask for. This
10
+ # is to avoid breaking things if we have a buggy coordinator that retries
11
+ # things over and over again.
12
+ MAX_COORDINATOR_RETRIES = 16
9
13
 
10
14
  ## Loads and returns the resource pointed to by +href+. The returned
11
15
  ## resource will be blessed into its "proper" class, if
12
16
  ## +self.class.namespace != nil+.
13
17
  def get
14
- self.response = faraday_connection.get(self.href || '')
15
- finish_up
18
+ execute_request { faraday_connection.get(href || '') }
16
19
  end
17
20
 
18
21
  ## By default, calls +post+ with the given arguments. Override to
@@ -23,12 +26,11 @@ class HyperResource
23
26
 
24
27
  ## POSTs the given attributes to this resource's href, and returns
25
28
  ## the response resource.
26
- def post(attrs=nil)
27
- attrs || self.attributes
28
- self.response = faraday_connection.post do |req|
29
- req.body = adapter.serialize(attrs)
29
+ def post(attrs = nil)
30
+ attrs ||= attributes
31
+ execute_request do
32
+ faraday_connection.post { |req| req.body = adapter.serialize(attrs) }
30
33
  end
31
- finish_up
32
34
  end
33
35
 
34
36
  ## By default, calls +put+ with the given arguments. Override to
@@ -40,48 +42,39 @@ class HyperResource
40
42
  ## PUTs this resource's attributes to this resource's href, and returns
41
43
  ## the response resource. If attributes are given, +put+ uses those
42
44
  ## instead.
43
- def put(attrs=nil)
44
- attrs ||= self.attributes
45
- self.response = faraday_connection.put do |req|
46
- req.body = adapter.serialize(attrs)
45
+ def put(attrs = nil)
46
+ attrs ||= attributes
47
+ execute_request do
48
+ faraday_connection.put { |req| req.body = adapter.serialize(attrs) }
47
49
  end
48
- finish_up
49
50
  end
50
51
 
51
52
  ## PATCHes this resource's changed attributes to this resource's href,
52
53
  ## and returns the response resource. If attributes are given, +patch+
53
54
  ## uses those instead.
54
- def patch(attrs=nil)
55
- attrs ||= self.attributes.changed_attributes
56
- self.response = faraday_connection.patch do |req|
57
- req.body = adapter.serialize(attrs)
55
+ def patch(attrs = nil)
56
+ attrs ||= attributes.changed_attributes
57
+ execute_request do
58
+ faraday_connection.patch { |req| req.body = adapter.serialize(attrs) }
58
59
  end
59
- finish_up
60
60
  end
61
61
 
62
62
  ## DELETEs this resource's href, and returns the response resource.
63
63
  def delete
64
- self.response = faraday_connection.delete
65
- finish_up
64
+ execute_request { faraday_connection.delete }
66
65
  end
67
66
 
68
67
  ## Returns a raw Faraday connection to this resource's URL, with proper
69
- ## headers (including auth). Threadsafe.
70
- def faraday_connection(url=nil)
71
- url ||= URI.join(self.root, self.href)
72
- key = Digest::MD5.hexdigest({
73
- 'faraday_connection' => {
74
- 'url' => url,
75
- 'headers' => self.headers,
76
- 'ba' => self.auth[:basic]
77
- }
78
- }.to_json)
79
- return Thread.current[key] if Thread.current[key]
80
-
81
- fc = Faraday.new(self.faraday_options.merge(:url => url)) do |builder|
82
- builder.headers.merge!('User-Agent' => "HyperResource #{HyperResource::VERSION}")
83
- builder.headers.merge!(self.headers || {})
84
- if (ba = self.auth[:basic])
68
+ ## headers (including auth).
69
+ def faraday_connection(url = nil)
70
+ url ||= URI.join(root, href)
71
+
72
+ Faraday.new(faraday_options.merge(url: url)) do |builder|
73
+ builder.headers.merge!(headers || {})
74
+ builder.headers['User-Agent'] = Aptible::Resource.configuration
75
+ .user_agent
76
+
77
+ if (ba = auth[:basic])
85
78
  builder.basic_auth(*ba)
86
79
  end
87
80
 
@@ -89,49 +82,66 @@ class HyperResource
89
82
  builder.request :retry
90
83
  builder.adapter Faraday.default_adapter
91
84
  end
92
-
93
- Thread.current[key] = fc
94
85
  end
95
86
 
96
- private
87
+ private
88
+
89
+ def execute_request
90
+ raise 'execute_request needs a block!' unless block_given?
91
+ retry_coordinator = Aptible::Resource.configuration
92
+ .retry_coordinator_class.new(self)
93
+
94
+ n_retry = 0
95
+ begin
96
+ finish_up(yield)
97
+ rescue HyperResource::ResponseError => e
98
+ n_retry += 1
99
+ raise e if n_retry > MAX_COORDINATOR_RETRIES
100
+ retry if retry_coordinator.retry?(e)
101
+ raise e
102
+ end
103
+ end
97
104
 
98
- def finish_up
105
+ def finish_up(response)
99
106
  begin
100
- self.body = self.adapter.deserialize(self.response.body) unless self.response.body.nil?
107
+ body = adapter.deserialize(response.body) unless response.body.nil?
101
108
  rescue StandardError => e
102
109
  raise HyperResource::ResponseError.new(
103
- "Error when deserializing response body",
104
- :response => self.response,
105
- :cause => e
110
+ 'Error when deserializing response body',
111
+ response: response,
112
+ cause: e
106
113
  )
107
114
  end
108
115
 
109
- self.adapter.apply(self.body, self)
110
- self.loaded = true
111
-
112
- status = self.response.status
116
+ status = response.status
113
117
  if status / 100 == 2
114
- return to_response_class
115
118
  elsif status / 100 == 3
116
- ## TODO redirect logic?
119
+ raise 'HyperResource does not handle redirects'
117
120
  elsif status / 100 == 4
118
121
  raise HyperResource::ClientError.new(status.to_s,
119
- :response => self.response,
120
- :body => self.body)
122
+ response: response,
123
+ body: body)
121
124
  elsif status / 100 == 5
122
125
  raise HyperResource::ServerError.new(status.to_s,
123
- :response => self.response,
124
- :body => self.body)
126
+ response: response,
127
+ body: body)
125
128
 
126
129
  else ## 1xx? really?
127
130
  raise HyperResource::ResponseError.new("Got status #{status}, wtf?",
128
- :response => self.response,
129
- :body => self.body)
131
+ response: response,
132
+ body: body)
130
133
 
131
134
  end
132
- end
133
135
 
136
+ # Unfortunately, HyperResource insists on having response and body
137
+ # be attributes..
138
+ self.response = response
139
+ self.body = body
140
+ adapter.apply(body, self)
141
+ self.loaded = true
142
+
143
+ to_response_class
144
+ end
134
145
  end
135
146
  end
136
147
  end
137
-
@@ -38,6 +38,7 @@ module HyperResource::Modules
38
38
  :namespace,
39
39
  :adapter,
40
40
  :faraday_options,
41
+ :token,
41
42
 
42
43
  :request,
43
44
  :response,
@@ -1,4 +1,4 @@
1
1
  class HyperResource
2
- VERSION = '0.2.5'
3
- VERSION_DATE = '2014-04-02'
2
+ VERSION = '0.2.5'.freeze
3
+ VERSION_DATE = '2014-04-02'.freeze
4
4
  end
@@ -35,7 +35,7 @@ describe Aptible::Resource::Base do
35
35
  allow_any_instance_of(klass).to receive(:find_by_url) do |u, _|
36
36
  calls << u
37
37
  page = pages[u]
38
- fail "Accessed unexpected URL #{u}" if page.nil?
38
+ raise "Accessed unexpected URL #{u}" if page.nil?
39
39
  page
40
40
  end
41
41
  end
@@ -146,14 +146,14 @@ describe Aptible::Resource::Base do
146
146
  end
147
147
 
148
148
  it 'should populate #errors in the event of an error' do
149
- mainframes_link.stub(:create) { fail hyperresource_exception }
149
+ mainframes_link.stub(:create) { raise hyperresource_exception }
150
150
  mainframe = Api::Mainframe.create
151
151
  expect(mainframe.errors.messages).to eq(base: 'Forbidden')
152
152
  expect(mainframe.errors.full_messages).to eq(['Forbidden'])
153
153
  end
154
154
 
155
155
  it 'should return a Base-classed resource on error' do
156
- mainframes_link.stub(:create) { fail hyperresource_exception }
156
+ mainframes_link.stub(:create) { raise hyperresource_exception }
157
157
  expect(Api::Mainframe.create).to be_a Api::Mainframe
158
158
  end
159
159
 
@@ -171,7 +171,7 @@ describe Aptible::Resource::Base do
171
171
  before { mainframes_link.stub(:create) { mainframe } }
172
172
 
173
173
  it 'should pass through any exceptions' do
174
- mainframes_link.stub(:create) { fail hyperresource_exception }
174
+ mainframes_link.stub(:create) { raise hyperresource_exception }
175
175
  expect do
176
176
  Api::Mainframe.create!
177
177
  end.to raise_error HyperResource::ResponseError
@@ -224,14 +224,14 @@ describe Aptible::Resource::Base do
224
224
 
225
225
  describe '#update' do
226
226
  it 'should populate #errors in the event of an error' do
227
- HyperResource.any_instance.stub(:put) { fail hyperresource_exception }
227
+ HyperResource.any_instance.stub(:put) { raise hyperresource_exception }
228
228
  subject.update({})
229
229
  expect(subject.errors.messages).to eq(base: 'Forbidden')
230
230
  expect(subject.errors.full_messages).to eq(['Forbidden'])
231
231
  end
232
232
 
233
233
  it 'should return false in the event of an error' do
234
- HyperResource.any_instance.stub(:put) { fail hyperresource_exception }
234
+ HyperResource.any_instance.stub(:put) { raise hyperresource_exception }
235
235
  expect(subject.update({})).to eq false
236
236
  end
237
237
 
@@ -243,7 +243,7 @@ describe Aptible::Resource::Base do
243
243
 
244
244
  describe '#update!' do
245
245
  it 'should populate #errors in the event of an error' do
246
- HyperResource.any_instance.stub(:put) { fail hyperresource_exception }
246
+ HyperResource.any_instance.stub(:put) { raise hyperresource_exception }
247
247
  begin
248
248
  subject.update!({})
249
249
  rescue
@@ -255,7 +255,7 @@ describe Aptible::Resource::Base do
255
255
  end
256
256
 
257
257
  it 'should pass through any exceptions' do
258
- HyperResource.any_instance.stub(:put) { fail hyperresource_exception }
258
+ HyperResource.any_instance.stub(:put) { raise hyperresource_exception }
259
259
  expect do
260
260
  subject.update!({})
261
261
  end.to raise_error HyperResource::ResponseError
@@ -271,7 +271,6 @@ describe Aptible::Resource::Base do
271
271
  let(:mainframe) { Api::Mainframe.new }
272
272
  let(:mainframes_link) { HyperResource::Link.new(href: '/mainframes') }
273
273
 
274
- before { Api.has_many :mainframes }
275
274
  before { subject.stub(:loaded) { true } }
276
275
  before { subject.stub(:links) { { mainframes: mainframes_link } } }
277
276
  before { mainframes_link.stub(:entries) { [mainframe] } }
@@ -279,19 +278,19 @@ describe Aptible::Resource::Base do
279
278
 
280
279
  describe '#create_#{relation}' do
281
280
  it 'should populate #errors in the event of an error' do
282
- mainframes_link.stub(:create) { fail hyperresource_exception }
281
+ mainframes_link.stub(:create) { raise hyperresource_exception }
283
282
  mainframe = subject.create_mainframe({})
284
283
  expect(mainframe.errors.messages).to eq(base: 'Forbidden')
285
284
  expect(mainframe.errors.full_messages).to eq(['Forbidden'])
286
285
  end
287
286
 
288
287
  it 'should return a Base-classed resource on error' do
289
- mainframes_link.stub(:create) { fail hyperresource_exception }
288
+ mainframes_link.stub(:create) { raise hyperresource_exception }
290
289
  expect(subject.create_mainframe.class).to eq Aptible::Resource::Base
291
290
  end
292
291
 
293
292
  it 'should have errors present on error' do
294
- mainframes_link.stub(:create) { fail hyperresource_exception }
293
+ mainframes_link.stub(:create) { raise hyperresource_exception }
295
294
  expect(subject.create_mainframe.errors.any?).to be true
296
295
  end
297
296
 
@@ -308,7 +307,7 @@ describe Aptible::Resource::Base do
308
307
 
309
308
  describe '#create_#{relation}!' do
310
309
  it 'should pass through any exceptions' do
311
- mainframes_link.stub(:create) { fail hyperresource_exception }
310
+ mainframes_link.stub(:create) { raise hyperresource_exception }
312
311
  expect do
313
312
  subject.create_mainframe!({})
314
313
  end.to raise_error HyperResource::ResponseError
@@ -387,4 +386,199 @@ describe Aptible::Resource::Base do
387
386
  expect(subject.awesome?).to be true
388
387
  end
389
388
  end
389
+
390
+ context 'configuration' do
391
+ subject { Api.new(root: 'http://foo.com') }
392
+
393
+ def configure_new_coordinator(&block)
394
+ Aptible::Resource.configure do |config|
395
+ config.retry_coordinator_class = \
396
+ Class.new(Aptible::Resource::DefaultRetryCoordinator) do
397
+ instance_exec(&block)
398
+ end
399
+ end
400
+ end
401
+
402
+ context 'retry_coordinator_class' do
403
+ it 'should not retry if the proc returns false' do
404
+ configure_new_coordinator { define_method(:retry?) { |_e| false } }
405
+
406
+ stub_request(:get, 'foo.com')
407
+ .to_return(body: { error: 'foo' }.to_json, status: 401).then
408
+ .to_return(body: { status: 'ok' }.to_json, status: 200)
409
+
410
+ expect { subject.get.body }
411
+ .to raise_error(HyperResource::ClientError, /foo/)
412
+ end
413
+
414
+ it 'should retry if the proc returns true' do
415
+ configure_new_coordinator { define_method(:retry?) { |_e| true } }
416
+
417
+ stub_request(:get, 'foo.com')
418
+ .to_return(body: { error: 'foo' }.to_json, status: 401).then
419
+ .to_return(body: { error: 'foo' }.to_json, status: 401).then
420
+ .to_return(body: { status: 'ok' }.to_json, status: 200)
421
+
422
+ expect(subject.get.body).to eq('status' => 'ok')
423
+ end
424
+
425
+ it 'should not retry if the request succeeds' do
426
+ failures = 0
427
+
428
+ configure_new_coordinator do
429
+ define_method(:retry?) { |_e| failures += 1 || true }
430
+ end
431
+
432
+ stub_request(:get, 'foo.com')
433
+ .to_return(body: { error: 'foo' }.to_json, status: 401).then
434
+ .to_return(body: { status: 'ok' }.to_json, status: 200).then
435
+ .to_return(body: { error: 'foo' }.to_json, status: 401)
436
+
437
+ expect(subject.get.body).to eq('status' => 'ok')
438
+
439
+ expect(failures).to eq(1)
440
+ end
441
+
442
+ it 'should not retry with the default proc' do
443
+ stub_request(:get, 'foo.com')
444
+ .to_return(body: { error: 'foo' }.to_json, status: 401).then
445
+ .to_return(body: { status: 'ok' }.to_json, status: 200)
446
+
447
+ expect { subject.get.body }
448
+ .to raise_error(HyperResource::ClientError, /foo/)
449
+ end
450
+
451
+ it 'should pass the resource in constructor and exception in method' do
452
+ resource = nil
453
+ exception = nil
454
+
455
+ configure_new_coordinator do
456
+ define_method(:initialize) { |r| resource = r }
457
+ define_method(:retry?) { |e| (exception = e) && false }
458
+ end
459
+
460
+ stub_request(:get, 'foo.com')
461
+ .to_return(body: { error: 'foo' }.to_json, status: 401)
462
+
463
+ expect { subject.get.body }
464
+ .to raise_error(HyperResource::ClientError, /foo/)
465
+
466
+ expect(resource).to be_a(Api)
467
+ expect(exception).to be_a(HyperResource::ClientError)
468
+ end
469
+
470
+ it 'should let the coordinator change e.g. the request token' do
471
+ subject.token = 'foo'
472
+ retry_was_called = false
473
+
474
+ configure_new_coordinator do
475
+ define_method(:retry?) do |_e|
476
+ resource.token = 'bar'
477
+ # resource.headers['Authorization'] = 'Bearer bar'
478
+ retry_was_called = true
479
+ end
480
+ end
481
+
482
+ stub_request(:get, 'foo.com')
483
+ .with(headers: { 'Authorization' => /foo/ })
484
+ .to_return(body: { error: 'foo' }.to_json, status: 401)
485
+
486
+ stub_request(:get, 'foo.com')
487
+ .with(headers: { 'Authorization' => /bar/ })
488
+ .to_return(body: { status: 'ok' }.to_json, status: 200)
489
+
490
+ expect(subject.get.body).to eq('status' => 'ok')
491
+ expect(retry_was_called).to be_truthy
492
+ end
493
+
494
+ it 'should eventually fail even if the coordinator wants to retry' do
495
+ n = 0
496
+
497
+ configure_new_coordinator do
498
+ define_method(:retry?) { |_e| n += 1 || true }
499
+ end
500
+
501
+ stub_request(:get, 'foo.com')
502
+ .to_return(body: { error: 'foo' }.to_json, status: 401)
503
+
504
+ expect { subject.get.body }
505
+ .to raise_error(HyperResource::ClientError, /foo/)
506
+
507
+ expect(n).to eq(HyperResource::Modules::HTTP::MAX_COORDINATOR_RETRIES)
508
+ end
509
+ end
510
+
511
+ context 'user_agent' do
512
+ it 'should update the user agent' do
513
+ Aptible::Resource.configure do |config|
514
+ config.user_agent = 'foo ua'
515
+ end
516
+
517
+ stub_request(:get, 'foo.com')
518
+ .with(headers: { 'User-Agent' => 'foo ua' })
519
+ .to_return(body: { status: 'ok' }.to_json, status: 200)
520
+
521
+ expect(subject.get.body).to eq('status' => 'ok')
522
+ end
523
+ end
524
+ end
525
+
526
+ context 'token' do
527
+ subject { Api.new(root: 'http://foo.com', token: 'bar') }
528
+
529
+ before do
530
+ stub_request(:get, 'foo.com/')
531
+ .with(headers: { 'Authorization' => /Bearer (bar|foo)/ })
532
+ .to_return(body: {
533
+ _links: { some: { href: 'http://foo.com/some' },
534
+ mainframes: { href: 'http://foo.com/mainframes' } },
535
+ _embedded: { best_mainframe: { _type: 'mainframe', status: 'ok' } }
536
+ }.to_json, status: 200)
537
+
538
+ stub_request(:get, 'foo.com/some')
539
+ .with(headers: { 'Authorization' => /Bearer (bar|foo)/ })
540
+ .to_return(body: { status: 'ok' }.to_json, status: 200)
541
+
542
+ stub_request(:get, 'foo.com/mainframes')
543
+ .with(headers: { 'Authorization' => /Bearer (bar|foo)/ })
544
+ .to_return(body: { _embedded: {
545
+ mainframes: [{ status: 'ok' }]
546
+ } }.to_json, status: 200)
547
+ end
548
+
549
+ it 'should persist the Authorization header when following links' do
550
+ expect(subject.some.get.body).to eq('status' => 'ok')
551
+ end
552
+
553
+ it 'should persist the token when following links' do
554
+ expect(subject.some.token).to eq('bar')
555
+ expect(subject.some.headers['Authorization']).to eq('Bearer bar')
556
+ end
557
+
558
+ it 'should set the Authorization header when setting the token' do
559
+ subject.token = 'foo'
560
+
561
+ expect(subject.some.token).to eq('foo')
562
+ expect(subject.some.headers['Authorization']).to eq('Bearer foo')
563
+ end
564
+
565
+ it 'should persist the token and set the Authorization header when ' \
566
+ 'initializing from resource' do
567
+ subject.get
568
+ r = Api.new(subject)
569
+
570
+ expect(r.token).to eq('bar')
571
+ expect(r.headers['Authorization']).to eq('Bearer bar')
572
+ end
573
+
574
+ it 'should persist the token when accessing a related collection' do
575
+ m = subject.mainframes.first
576
+ expect(m.token).to eq('bar')
577
+ end
578
+
579
+ it 'should persist the token when accessing a named embedded object' do
580
+ m = subject.best_mainframe
581
+ expect(m.token).to eq('bar')
582
+ end
583
+ end
390
584
  end
@@ -9,12 +9,6 @@ describe Aptible::Resource::Base do
9
9
  subject { Api.new(root: "http://#{domain}") }
10
10
 
11
11
  context 'with mock connections' do
12
- around do |example|
13
- WebMock.disable_net_connect!
14
- example.run
15
- WebMock.allow_net_connect!
16
- end
17
-
18
12
  it 'should retry timeout errors' do
19
13
  stub_request(:get, domain)
20
14
  .to_timeout.then
@@ -34,6 +28,12 @@ describe Aptible::Resource::Base do
34
28
  end
35
29
 
36
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
37
  it 'default to 10 seconds of timeout and retry 3 times' do
38
38
  # This really relies on how exactly MRI implements Net::HTTP open timeouts
39
39
  skip 'MRI implementation-specific' if RUBY_PLATFORM == 'java'
data/spec/fixtures/api.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'aptible/resource'
2
2
 
3
3
  class Api < Aptible::Resource::Base
4
+ has_many :mainframes
5
+ embeds_one :best_mainframe
6
+
4
7
  def namespace
5
8
  'Api'
6
9
  end
data/spec/spec_helper.rb CHANGED
@@ -15,4 +15,9 @@ require 'aptible/resource'
15
15
 
16
16
  # Webmock
17
17
  require 'webmock/rspec'
18
- WebMock.allow_net_connect!
18
+ WebMock.disable_net_connect!
19
+
20
+ RSpec.configure do |config|
21
+ config.before { Aptible::Resource.configuration.reset }
22
+ config.before { WebMock.reset! }
23
+ 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.6
4
+ version: 0.3.7
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-06-16 00:00:00.000000000 Z
11
+ date: 2016-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uri_template
@@ -70,16 +70,30 @@ dependencies:
70
70
  name: activesupport
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '4.0'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '4.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: gem_config
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.3.1
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.3.1
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: bundler
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -184,6 +198,7 @@ files:
184
198
  - lib/aptible/resource/adapter.rb
185
199
  - lib/aptible/resource/base.rb
186
200
  - lib/aptible/resource/boolean.rb
201
+ - lib/aptible/resource/default_retry_coordinator.rb
187
202
  - lib/aptible/resource/errors.rb
188
203
  - lib/aptible/resource/ext/faraday.rb
189
204
  - lib/aptible/resource/version.rb
@@ -225,7 +240,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
225
240
  version: '0'
226
241
  requirements: []
227
242
  rubyforge_project:
228
- rubygems_version: 2.6.4
243
+ rubygems_version: 2.4.5.1
229
244
  signing_key:
230
245
  specification_version: 4
231
246
  summary: Foundation classes for Aptible resource server gems