aptible-resource 1.0.2 → 1.1.0.pre.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: 8763d2caea0e00ee89256be017ffc5276aef0e80
4
- data.tar.gz: 5bd0eef4af8fe6bf6e20cbbae9023f5fbfdb461a
3
+ metadata.gz: a73b16e30cf7b0bb43875b1836684ef93844e8b6
4
+ data.tar.gz: 8f1ab370731c0eab7af7ad16e672186f9a21e265
5
5
  SHA512:
6
- metadata.gz: 79805e68facedfce6282d7f0f945b07df459478929a01ada4e6b76f5c88e20ac8ddacfa25bb51cbfc9a11953591aed06cd7c6976cfb709d0cfb40957607c4113
7
- data.tar.gz: 2deab0f8a6ca743f987e9beffe1e7ddfccb0c9287cfa853a82be74b6d822da3bee1eae08e7bdcbe8aa0e84d7c5a79a6bc1c9cf9e885d45b28ca89c8cf50ef9da
6
+ metadata.gz: ebfa485b577102776eee1481fbf4da3e0cb220dbb36e95a5af39148af7b72150645d6b0ffcdc2125d832fa1ae6613d06edfc9f46c70970ccd34fd8ceee5071ac
7
+ data.tar.gz: dde6aa360f1567af93327340ddd05493cdfbb9d19953692f72dff5fea8bed638c3a06d44061885ddb0f63108b56b90c21b072fb497d55d710b10b8dade111462
data/.travis.yml CHANGED
@@ -5,4 +5,3 @@ rvm:
5
5
  - "2.1"
6
6
  - "2.2"
7
7
  - "2.3"
8
- - jruby-9.1.13.0
@@ -21,9 +21,8 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  # HyperResource dependencies
23
23
  spec.add_dependency 'uri_template', '>= 0.5.2'
24
- spec.add_dependency 'faraday', '>= 0.9.2', '< 0.14'
25
24
  spec.add_dependency 'json'
26
-
25
+ spec.add_dependency 'httpclient', '~> 2.8'
27
26
  spec.add_dependency 'fridge'
28
27
  spec.add_dependency 'activesupport', '>= 4.0', '< 6.0'
29
28
  spec.add_dependency 'gem_config', '~> 0.3.1'
@@ -3,6 +3,7 @@ require 'aptible/resource/base'
3
3
  require 'aptible/resource/default_retry_coordinator'
4
4
  require 'aptible/resource/null_retry_coordinator'
5
5
  require 'gem_config'
6
+ require 'logger'
6
7
 
7
8
  module Aptible
8
9
  module Resource
@@ -18,6 +19,10 @@ module Aptible
18
19
  has :user_agent,
19
20
  classes: [String],
20
21
  default: "aptible-resource #{Aptible::Resource::VERSION}"
22
+
23
+ has :logger,
24
+ classes: [Logger],
25
+ default: Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
21
26
  end
22
27
 
23
28
  class << self
@@ -224,16 +224,6 @@ module Aptible
224
224
  end
225
225
  end
226
226
 
227
- def self.faraday_options
228
- # Default Faraday options. May be overridden by passing
229
- # faraday_options to the initializer.
230
- {
231
- request: {
232
- open_timeout: 10
233
- }
234
- }
235
- end
236
-
237
227
  def initialize(options = {})
238
228
  return super(options) unless options.is_a?(Hash)
239
229
 
@@ -305,7 +295,7 @@ module Aptible
305
295
  # Already deleted
306
296
  raise unless e.response.status == 404
307
297
  rescue HyperResource::ResponseError
308
- # HyperResource/Faraday choke on empty response bodies
298
+ # HyperResource chokes on empty response bodies
309
299
  nil
310
300
  end
311
301
 
@@ -5,11 +5,27 @@ module Aptible
5
5
 
6
6
  IDEMPOTENT_METHODS = [
7
7
  # Idempotent as per RFC
8
- :delete, :get, :head, :options, :put,
8
+ 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT',
9
+
9
10
  # Idempotent on our APIs
10
- :patch
11
+ 'PATCH'
12
+ ].freeze
13
+
14
+ RETRY_ERRORS = [
15
+ # Ancestor for Errno::X
16
+ SystemCallError,
17
+
18
+ # Might be caused by e.g. DNS failure
19
+ SocketError,
20
+
21
+ # HTTPClient transfer error
22
+ HTTPClient::TimeoutError,
23
+ HTTPClient::KeepAliveDisconnected,
24
+ HTTPClient::BadResponseError,
25
+
26
+ # Bad response
27
+ HyperResource::ServerError
11
28
  ].freeze
12
- RETRY_ERRORS = [Faraday::Error, HyperResource::ServerError].freeze
13
29
 
14
30
  def initialize(resource)
15
31
  @resource = resource
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module Resource
3
- VERSION = '1.0.2'.freeze
3
+ VERSION = '1.1.0.pre.0'.freeze
4
4
  end
5
5
  end
@@ -46,9 +46,6 @@ public
46
46
  ## [headers] Headers to send along with requests for this resource (as
47
47
  ## well as its eventual child resources, if any).
48
48
  ##
49
- ## [faraday_options] Configuration passed to +Faraday::Connection.initialize+,
50
- ## such as +{request: {timeout: 30}}+.
51
- ##
52
49
  def initialize(opts={})
53
50
  return init_from_resource(opts) if opts.kind_of?(HyperResource)
54
51
 
@@ -58,8 +55,6 @@ public
58
55
  self.namespace = opts[:namespace] || self.class.namespace
59
56
  self.headers = DEFAULT_HEADERS.merge(self.class.headers || {}).
60
57
  merge(opts[:headers] || {})
61
- self.faraday_options = opts[:faraday_options] ||
62
- self.class.faraday_options || {}
63
58
 
64
59
  ## There's a little acrobatics in getting Attributes, Links, and Objects
65
60
  ## into the correct subclass.
@@ -130,14 +125,14 @@ public
130
125
  ## in this resource. Returns nil on failure.
131
126
  def [](i)
132
127
  get unless loaded
133
- self.objects.first[1][i] rescue nil
128
+ self.objects.first[1][i]
134
129
  end
135
130
 
136
131
  ## Iterates over the objects in the first collection of embedded objects
137
132
  ## in this resource.
138
133
  def each(&block)
139
134
  get unless loaded
140
- self.objects.first[1].each(&block) rescue nil
135
+ self.objects.first[1].each(&block)
141
136
  end
142
137
 
143
138
  #### Magic
@@ -199,7 +194,6 @@ public
199
194
  :auth => self.auth,
200
195
  :headers => self.headers,
201
196
  :namespace => self.namespace,
202
- :faraday_options => self.faraday_options,
203
197
  :token => self.token,
204
198
  :href => href)
205
199
  end
@@ -253,7 +247,6 @@ public
253
247
  self.class.response_class(self.response, self.namespace)
254
248
  end
255
249
 
256
-
257
250
  ## Inspects the given Faraday::Response, and returns a string describing
258
251
  ## this resource's data type.
259
252
  ##
@@ -10,7 +10,7 @@ class HyperResource
10
10
  end
11
11
 
12
12
  class ResponseError < Exception
13
- ## The +Faraday::Response+ object which led to this exception.
13
+ ## The +HTTPClient::Message+ object which led to this exception.
14
14
  attr_accessor :response
15
15
 
16
16
  ## The deserialized response body which led to this exception.
@@ -53,7 +53,7 @@ class HyperResource::Link
53
53
 
54
54
  ## If we were called with a method we don't know, load this resource
55
55
  ## and pass the message along. This achieves implicit loading.
56
- def method_missing(method, *args)
57
- self.get.send(method, *args)
56
+ def method_missing(method, *args, &block)
57
+ self.get.send(method, *args, &block)
58
58
  end
59
59
  end
@@ -1,4 +1,4 @@
1
- require 'faraday'
1
+ require 'httpclient'
2
2
  require 'uri'
3
3
  require 'json'
4
4
  require 'digest/md5'
@@ -11,11 +11,42 @@ class HyperResource
11
11
  # things over and over again.
12
12
  MAX_COORDINATOR_RETRIES = 16
13
13
 
14
+ CONTENT_TYPE_HEADERS = {
15
+ 'Content-Type' => 'application/json; charset=utf-8'
16
+ }.freeze
17
+
18
+ class << self
19
+ attr_reader :http_client
20
+
21
+ def initialize_http_client!
22
+ @http_client = HTTPClient.new.tap do |c|
23
+ c.cookie_manager = nil
24
+ c.connect_timeout = 30
25
+ c.send_timeout = 45
26
+ c.receive_timeout = 30
27
+ c.keep_alive_timeout = 15
28
+ c.ssl_config.set_default_paths
29
+ end
30
+ end
31
+ end
32
+
33
+ # We use this accessor / initialize as opposed to a simple constant
34
+ # because during specs, Webmock stubs the HTTPClient class, but that's
35
+ # happens after we initialized the constant (we could work around that
36
+ # by loading Webmock first, but this is just as simple.
37
+ initialize_http_client!
38
+
14
39
  ## Loads and returns the resource pointed to by +href+. The returned
15
40
  ## resource will be blessed into its "proper" class, if
16
41
  ## +self.class.namespace != nil+.
17
42
  def get
18
- execute_request { faraday_connection.get(href || '') }
43
+ execute_request('GET') do |uri, headers|
44
+ HTTP.http_client.get(
45
+ uri,
46
+ follow_redirect: true,
47
+ header: headers
48
+ )
49
+ end
19
50
  end
20
51
 
21
52
  ## By default, calls +post+ with the given arguments. Override to
@@ -28,8 +59,13 @@ class HyperResource
28
59
  ## the response resource.
29
60
  def post(attrs = nil)
30
61
  attrs ||= attributes
31
- execute_request do
32
- faraday_connection.post { |req| req.body = adapter.serialize(attrs) }
62
+
63
+ execute_request('POST') do |uri, headers|
64
+ HTTP.http_client.post(
65
+ uri,
66
+ body: adapter.serialize(attrs),
67
+ header: headers.merge(CONTENT_TYPE_HEADERS)
68
+ )
33
69
  end
34
70
  end
35
71
 
@@ -44,8 +80,13 @@ class HyperResource
44
80
  ## instead.
45
81
  def put(attrs = nil)
46
82
  attrs ||= attributes
47
- execute_request do
48
- faraday_connection.put { |req| req.body = adapter.serialize(attrs) }
83
+
84
+ execute_request('PUT') do |uri, headers|
85
+ HTTP.http_client.put(
86
+ uri,
87
+ body: adapter.serialize(attrs),
88
+ header: headers.merge(CONTENT_TYPE_HEADERS)
89
+ )
49
90
  end
50
91
  end
51
92
 
@@ -54,49 +95,62 @@ class HyperResource
54
95
  ## uses those instead.
55
96
  def patch(attrs = nil)
56
97
  attrs ||= attributes.changed_attributes
57
- execute_request do
58
- faraday_connection.patch { |req| req.body = adapter.serialize(attrs) }
98
+
99
+ execute_request('PATCH') do |uri, headers|
100
+ HTTP.http_client.patch(
101
+ uri,
102
+ body: adapter.serialize(attrs),
103
+ header: headers.merge(CONTENT_TYPE_HEADERS)
104
+ )
59
105
  end
60
106
  end
61
107
 
62
108
  ## DELETEs this resource's href, and returns the response resource.
63
109
  def delete
64
- execute_request { faraday_connection.delete }
65
- end
66
-
67
- ## Returns a raw Faraday connection to this resource's URL, with proper
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])
78
- builder.basic_auth(*ba)
79
- end
80
-
81
- builder.use WrapErrors # This has to be first!
82
- builder.request :url_encoded
83
- builder.adapter Faraday.default_adapter
110
+ execute_request('DELETE') do |uri, headers|
111
+ HTTP.http_client.delete(uri, header: headers)
84
112
  end
85
113
  end
86
114
 
87
115
  private
88
116
 
89
- def execute_request
117
+ def execute_request(method)
90
118
  raise 'execute_request needs a block!' unless block_given?
91
119
  retry_coordinator = Aptible::Resource.retry_coordinator_class.new(self)
92
120
 
121
+ uri = URI.join(root, href)
122
+
123
+ h = headers || {}
124
+ h['User-Agent'] = Aptible::Resource.configuration.user_agent
125
+
93
126
  n_retry = 0
94
127
 
95
128
  begin
129
+ t0 = Time.now
130
+
96
131
  begin
97
- finish_up(yield)
98
- rescue HyperResource::ResponseError => e
99
- raise WrapErrors::WrappedError.new(e.response.env.method, e)
132
+ res = yield(uri, h)
133
+ entity = finish_up(res)
134
+ rescue StandardError => e
135
+ Aptible::Resource.configuration.logger.info([
136
+ method,
137
+ uri,
138
+ "(#{n_retry})",
139
+ "#{(Time.now - t0).round(2)}s",
140
+ "ERR[#{e.class}: #{e}]"
141
+ ].join(' '))
142
+
143
+ raise WrapErrors::WrappedError.new(method, e)
144
+ else
145
+ Aptible::Resource.configuration.logger.info([
146
+ method,
147
+ uri,
148
+ "(#{n_retry})",
149
+ "#{(Time.now - t0).round(2)}s",
150
+ res.status
151
+ ].join(' '))
152
+
153
+ entity
100
154
  end
101
155
  rescue WrapErrors::WrappedError => e
102
156
  n_retry += 1
@@ -121,18 +175,23 @@ class HyperResource
121
175
  elsif status / 100 == 3
122
176
  raise 'HyperResource does not handle redirects'
123
177
  elsif status / 100 == 4
124
- raise HyperResource::ClientError.new(status.to_s,
125
- response: response,
126
- body: body)
178
+ raise HyperResource::ClientError.new(
179
+ status.to_s,
180
+ response: response,
181
+ body: body
182
+ )
127
183
  elsif status / 100 == 5
128
- raise HyperResource::ServerError.new(status.to_s,
129
- response: response,
130
- body: body)
131
-
184
+ raise HyperResource::ServerError.new(
185
+ status.to_s,
186
+ response: response,
187
+ body: body
188
+ )
132
189
  else ## 1xx? really?
133
- raise HyperResource::ResponseError.new("Got status #{status}, wtf?",
134
- response: response,
135
- body: body)
190
+ raise HyperResource::ResponseError.new(
191
+ "Got status #{status}, wtf?",
192
+ response: response,
193
+ body: body
194
+ )
136
195
 
137
196
  end
138
197
 
@@ -1,7 +1,7 @@
1
1
  class HyperResource
2
2
  module Modules
3
3
  module HTTP
4
- class WrapErrors < Faraday::Middleware
4
+ module WrapErrors
5
5
  class WrappedError < StandardError
6
6
  attr_reader :method, :err
7
7
 
@@ -10,12 +10,6 @@ class HyperResource
10
10
  @err = err
11
11
  end
12
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
13
  end
20
14
  end
21
15
  end
@@ -26,7 +26,6 @@ module HyperResource::Modules
26
26
  :headers, ## e.g. {'Accept' => 'application/vnd.example+json'}
27
27
  :namespace, ## e.g. 'ExampleAPI', or the class ExampleAPI itself
28
28
  :adapter, ## subclass of HR::Adapter
29
- :faraday_options ## e.g. {:request => {:timeout => 30}}
30
29
  ]
31
30
  end
32
31
 
@@ -37,7 +36,6 @@ module HyperResource::Modules
37
36
  :headers,
38
37
  :namespace,
39
38
  :adapter,
40
- :faraday_options,
41
39
  :token,
42
40
 
43
41
  :request,
@@ -52,16 +52,31 @@ describe Aptible::Resource::Base do
52
52
  end
53
53
 
54
54
  describe '.find' do
55
- it 'should call find_by_url' do
56
- url = '/mainframes/42'
57
- expect(Api::Mainframe).to receive(:find_by_url).with url, {}
58
- Api::Mainframe.find(42)
55
+ it 'should find' do
56
+ stub_request(
57
+ :get, 'https://resource.example.com/mainframes/42'
58
+ ).to_return(body: { id: 42 }.to_json, status: 200)
59
+
60
+ m = Api::Mainframe.find(42)
61
+ expect(m.id).to eq(42)
59
62
  end
60
63
 
61
- it 'should call find_by_url with query params' do
62
- url = '/mainframes/42?test=123'
63
- expect(Api::Mainframe).to receive(:find_by_url).with url, test: 123
64
- Api::Mainframe.find(42, test: 123)
64
+ it 'should find with query params' do
65
+ stub_request(
66
+ :get, 'https://resource.example.com/mainframes/42?test=123'
67
+ ).to_return(body: { id: 42 }.to_json, status: 200)
68
+
69
+ m = Api::Mainframe.find(42, test: 123)
70
+ expect(m.id).to eq(42)
71
+ end
72
+
73
+ it 'should return an instance of the correct class' do
74
+ stub_request(
75
+ :get, 'https://resource.example.com/mainframes/42'
76
+ ).to_return(body: { id: 42 }.to_json, status: 200)
77
+
78
+ m = Api::Mainframe.find(42)
79
+ expect(m).to be_a(Api::Mainframe)
65
80
  end
66
81
  end
67
82
 
@@ -453,7 +468,7 @@ describe Aptible::Resource::Base do
453
468
  end
454
469
 
455
470
  context 'configuration' do
456
- subject { Api.new(root: 'http://foo.com') }
471
+ subject { Api.new(root: 'http://example.com') }
457
472
 
458
473
  def configure_new_coordinator(&block)
459
474
  Aptible::Resource.configure do |config|
@@ -468,7 +483,7 @@ describe Aptible::Resource::Base do
468
483
  it 'should not retry if the proc returns false' do
469
484
  configure_new_coordinator { define_method(:retry?) { |_, _e| false } }
470
485
 
471
- stub_request(:get, 'foo.com')
486
+ stub_request(:get, 'example.com')
472
487
  .to_return(body: { error: 'foo' }.to_json, status: 401).then
473
488
  .to_return(body: { status: 'ok' }.to_json, status: 200)
474
489
 
@@ -479,7 +494,7 @@ describe Aptible::Resource::Base do
479
494
  it 'should retry if the proc returns true' do
480
495
  configure_new_coordinator { define_method(:retry?) { |_, _e| true } }
481
496
 
482
- stub_request(:get, 'foo.com')
497
+ stub_request(:get, 'example.com')
483
498
  .to_return(body: { error: 'foo' }.to_json, status: 401).then
484
499
  .to_return(body: { error: 'foo' }.to_json, status: 401).then
485
500
  .to_return(body: { status: 'ok' }.to_json, status: 200)
@@ -494,7 +509,7 @@ describe Aptible::Resource::Base do
494
509
  define_method(:retry?) { |_, _e| failures += 1 || true }
495
510
  end
496
511
 
497
- stub_request(:get, 'foo.com')
512
+ stub_request(:get, 'example.com')
498
513
  .to_return(body: { error: 'foo' }.to_json, status: 401).then
499
514
  .to_return(body: { status: 'ok' }.to_json, status: 200).then
500
515
  .to_return(body: { error: 'foo' }.to_json, status: 401)
@@ -505,7 +520,7 @@ describe Aptible::Resource::Base do
505
520
  end
506
521
 
507
522
  it 'should not retry with the default proc' do
508
- stub_request(:get, 'foo.com')
523
+ stub_request(:get, 'example.com')
509
524
  .to_return(body: { error: 'foo' }.to_json, status: 401).then
510
525
  .to_return(body: { status: 'ok' }.to_json, status: 200)
511
526
 
@@ -522,7 +537,7 @@ describe Aptible::Resource::Base do
522
537
  define_method(:retry?) { |_, e| (exception = e) && false }
523
538
  end
524
539
 
525
- stub_request(:get, 'foo.com')
540
+ stub_request(:get, 'example.com')
526
541
  .to_return(body: { error: 'foo' }.to_json, status: 401)
527
542
 
528
543
  expect { subject.get.body }
@@ -544,11 +559,11 @@ describe Aptible::Resource::Base do
544
559
  end
545
560
  end
546
561
 
547
- stub_request(:get, 'foo.com')
562
+ stub_request(:get, 'example.com')
548
563
  .with(headers: { 'Authorization' => /foo/ })
549
564
  .to_return(body: { error: 'foo' }.to_json, status: 401)
550
565
 
551
- stub_request(:get, 'foo.com')
566
+ stub_request(:get, 'example.com')
552
567
  .with(headers: { 'Authorization' => /bar/ })
553
568
  .to_return(body: { status: 'ok' }.to_json, status: 200)
554
569
 
@@ -563,7 +578,7 @@ describe Aptible::Resource::Base do
563
578
  define_method(:retry?) { |_, _e| n += 1 || true }
564
579
  end
565
580
 
566
- stub_request(:get, 'foo.com')
581
+ stub_request(:get, 'example.com')
567
582
  .to_return(body: { error: 'foo' }.to_json, status: 401)
568
583
 
569
584
  expect { subject.get.body }
@@ -579,7 +594,7 @@ describe Aptible::Resource::Base do
579
594
  config.user_agent = 'foo ua'
580
595
  end
581
596
 
582
- stub_request(:get, 'foo.com')
597
+ stub_request(:get, 'example.com')
583
598
  .with(headers: { 'User-Agent' => 'foo ua' })
584
599
  .to_return(body: { status: 'ok' }.to_json, status: 200)
585
600
 
@@ -589,22 +604,22 @@ describe Aptible::Resource::Base do
589
604
  end
590
605
 
591
606
  context 'token' do
592
- subject { Api.new(root: 'http://foo.com', token: 'bar') }
607
+ subject { Api.new(root: 'http://example.com', token: 'bar') }
593
608
 
594
609
  before do
595
- stub_request(:get, 'foo.com/')
610
+ stub_request(:get, 'example.com/')
596
611
  .with(headers: { 'Authorization' => /Bearer (bar|foo)/ })
597
612
  .to_return(body: {
598
- _links: { some: { href: 'http://foo.com/some' },
599
- mainframes: { href: 'http://foo.com/mainframes' } },
613
+ _links: { some: { href: 'http://example.com/some' },
614
+ mainframes: { href: 'http://example.com/mainframes' } },
600
615
  _embedded: { best_mainframe: { _type: 'mainframe', status: 'ok' } }
601
616
  }.to_json, status: 200)
602
617
 
603
- stub_request(:get, 'foo.com/some')
618
+ stub_request(:get, 'example.com/some')
604
619
  .with(headers: { 'Authorization' => /Bearer (bar|foo)/ })
605
620
  .to_return(body: { status: 'ok' }.to_json, status: 200)
606
621
 
607
- stub_request(:get, 'foo.com/mainframes')
622
+ stub_request(:get, 'example.com/mainframes')
608
623
  .with(headers: { 'Authorization' => /Bearer (bar|foo)/ })
609
624
  .to_return(body: { _embedded: {
610
625
  mainframes: [{ status: 'ok' }]
@@ -646,4 +661,85 @@ describe Aptible::Resource::Base do
646
661
  expect(m.token).to eq('bar')
647
662
  end
648
663
  end
664
+
665
+ context 'lazy fetching' do
666
+ subject { Api.new(root: 'http://foo.com') }
667
+
668
+ it 'should support enumerable methods' do
669
+ index = {
670
+ _links: {
671
+ some_items: { href: 'http://foo.com/some_items' }
672
+ }
673
+ }
674
+
675
+ some_items = {
676
+ _embedded: {
677
+ some_items: [
678
+ { id: 1, handle: 'foo' },
679
+ { id: 2, handle: 'bar' },
680
+ { id: 3, handle: 'qux' }
681
+ ]
682
+ }
683
+ }
684
+
685
+ stub_request(:get, 'foo.com')
686
+ .to_return(body: index.to_json, status: 200)
687
+
688
+ stub_request(:get, 'foo.com/some_items')
689
+ .to_return(body: some_items.to_json, status: 200)
690
+
691
+ bar = subject.some_items.find { |m| m.id == 2 }
692
+ expect(bar.handle).to eq('bar')
693
+ end
694
+ end
695
+
696
+ describe '_type' do
697
+ subject { Api.new(root: 'http://example.com', token: 'bar') }
698
+
699
+ it 'uses the correct class for an expected linked instance' do
700
+ stub_request(:get, 'example.com/')
701
+ .to_return(body: {
702
+ _links: {
703
+ worst_mainframe: { href: 'http://example.com/mainframes/123' }
704
+ }
705
+ }.to_json, status: 200)
706
+
707
+ stub_request(:get, 'example.com/mainframes/123')
708
+ .to_return(body: { _type: 'mainframe', id: 123 }.to_json, status: 200)
709
+
710
+ expect(subject.worst_mainframe).to be_a(Api::Mainframe)
711
+ end
712
+
713
+ it 'uses the correct class for an unexpected linked instance' do
714
+ stub_request(:get, 'example.com/')
715
+ .to_return(body: {
716
+ _links: {
717
+ some: { href: 'http://example.com/mainframes/123' }
718
+ }
719
+ }.to_json, status: 200)
720
+
721
+ stub_request(:get, 'example.com/mainframes/123')
722
+ .to_return(body: { _type: 'mainframe', id: 123 }.to_json, status: 200)
723
+
724
+ expect(subject.some.get).to be_a(Api::Mainframe)
725
+ end
726
+
727
+ it 'uses the correct class for an expected embedded instance' do
728
+ stub_request(:get, 'example.com/')
729
+ .to_return(body: {
730
+ _embedded: { best_mainframe: { _type: 'mainframe', id: 123 } }
731
+ }.to_json, status: 200)
732
+
733
+ expect(subject.best_mainframe).to be_a(Api::Mainframe)
734
+ end
735
+
736
+ it 'uses the correct class for an unexpected embedded instance' do
737
+ stub_request(:get, 'example.com/')
738
+ .to_return(body: {
739
+ _embedded: { some: { _type: 'mainframe', id: 123 } }
740
+ }.to_json, status: 200)
741
+
742
+ expect(subject.some).to be_a(Api::Mainframe)
743
+ end
744
+ end
649
745
  end
@@ -136,15 +136,6 @@ describe Aptible::Resource::Base do
136
136
  expect(subject.get.body).to eq(body)
137
137
  end
138
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
139
  it 'should retry connection errors' do
149
140
  stub_request(:get, href)
150
141
  .to_raise(Errno::ECONNREFUSED).then
@@ -159,29 +150,8 @@ describe Aptible::Resource::Base do
159
150
  .to_timeout.then
160
151
  .to_return(body: json_body)
161
152
 
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)
153
+ expect { subject.post }
154
+ .to raise_error(HTTPClient::TimeoutError)
185
155
  end
186
156
  end
187
157
  end
data/spec/fixtures/api.rb CHANGED
@@ -4,6 +4,7 @@ class Api < Aptible::Resource::Base
4
4
  has_many :mainframes
5
5
  embeds_many :embedded_mainframes
6
6
  embeds_one :best_mainframe
7
+ has_one :worst_mainframe
7
8
 
8
9
  def namespace
9
10
  'Api'
data/spec/spec_helper.rb CHANGED
@@ -18,6 +18,8 @@ require 'webmock/rspec'
18
18
  WebMock.disable_net_connect!
19
19
 
20
20
  RSpec.configure do |config|
21
+ config.before(:suite) { HyperResource::Modules::HTTP.initialize_http_client! }
22
+
21
23
  config.before { Aptible::Resource.configuration.reset }
22
24
  config.before { WebMock.reset! }
23
25
 
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: 1.0.2
4
+ version: 1.1.0.pre.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: 2017-11-16 00:00:00.000000000 Z
11
+ date: 2018-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uri_template
@@ -25,39 +25,33 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.5.2
27
27
  - !ruby/object:Gem::Dependency
28
- name: faraday
28
+ name: json
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.9.2
34
- - - "<"
35
- - !ruby/object:Gem::Version
36
- version: '0.14'
33
+ version: '0'
37
34
  type: :runtime
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
38
  - - ">="
42
39
  - !ruby/object:Gem::Version
43
- version: 0.9.2
44
- - - "<"
45
- - !ruby/object:Gem::Version
46
- version: '0.14'
40
+ version: '0'
47
41
  - !ruby/object:Gem::Dependency
48
- name: json
42
+ name: httpclient
49
43
  requirement: !ruby/object:Gem::Requirement
50
44
  requirements:
51
- - - ">="
45
+ - - "~>"
52
46
  - !ruby/object:Gem::Version
53
- version: '0'
47
+ version: '2.8'
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
51
  requirements:
58
- - - ">="
52
+ - - "~>"
59
53
  - !ruby/object:Gem::Version
60
- version: '0'
54
+ version: '2.8'
61
55
  - !ruby/object:Gem::Dependency
62
56
  name: fridge
63
57
  requirement: !ruby/object:Gem::Requirement
@@ -249,12 +243,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
249
243
  version: '0'
250
244
  required_rubygems_version: !ruby/object:Gem::Requirement
251
245
  requirements:
252
- - - ">="
246
+ - - ">"
253
247
  - !ruby/object:Gem::Version
254
- version: '0'
248
+ version: 1.3.1
255
249
  requirements: []
256
250
  rubyforge_project:
257
- rubygems_version: 2.6.13
251
+ rubygems_version: 2.4.5.3
258
252
  signing_key:
259
253
  specification_version: 4
260
254
  summary: Foundation classes for Aptible resource server gems