aptible-resource 1.0.2 → 1.1.0.pre.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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