my_john_deere_api 2.3.4 → 2.5.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/README.md +111 -1
- data/lib/my_john_deere_api.rb +1 -0
- data/lib/my_john_deere_api/client.rb +13 -3
- data/lib/my_john_deere_api/net_http_retry.rb +4 -0
- data/lib/my_john_deere_api/net_http_retry/decorator.rb +62 -0
- data/lib/my_john_deere_api/net_http_retry/invalid_response_error.rb +23 -0
- data/lib/my_john_deere_api/net_http_retry/max_retries_exceeded_error.rb +20 -0
- data/lib/my_john_deere_api/version.rb +1 -1
- data/test/lib/my_john_deere_api/client_test.rb +20 -3
- data/test/lib/my_john_deere_api/net_http_retry/decorator_test.rb +208 -0
- data/test/lib/my_john_deere_api/net_http_retry/invalid_response_error_test.rb +19 -0
- data/test/lib/my_john_deere_api/net_http_retry/max_retries_exceeded_error_test.rb +13 -0
- data/test/support/helper.rb +1 -0
- data/test/support/vcr/accessor/delete_failed.yml +327 -0
- data/test/support/vcr/accessor/delete_invalid.yml +39 -0
- data/test/support/vcr/accessor/delete_max_failed.yml +615 -0
- data/test/support/vcr/accessor/delete_retry.yml +191 -0
- data/test/support/vcr/accessor/delete_retry_too_soon.yml +191 -0
- data/test/support/vcr/accessor/get_failed.yml +390 -0
- data/test/support/vcr/accessor/get_invalid.yml +46 -0
- data/test/support/vcr/accessor/get_max_failed.yml +734 -0
- data/test/support/vcr/accessor/get_retry.yml +226 -0
- data/test/support/vcr/accessor/get_retry_too_soon.yml +226 -0
- data/test/support/vcr/accessor/post_failed.yml +417 -0
- data/test/support/vcr/accessor/post_invalid.yml +49 -0
- data/test/support/vcr/accessor/post_max_failed.yml +785 -0
- data/test/support/vcr/accessor/post_retry.yml +241 -0
- data/test/support/vcr/accessor/post_retry_too_soon.yml +241 -0
- data/test/support/vcr/accessor/put_failed.yml +372 -0
- data/test/support/vcr/accessor/put_invalid.yml +44 -0
- data/test/support/vcr/accessor/put_max_failed.yml +700 -0
- data/test/support/vcr/accessor/put_retry.yml +216 -0
- data/test/support/vcr/accessor/put_retry_too_soon.yml +216 -0
- metadata +30 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba69ef151796a688f69eba0f6a396d277baa893aa9e589922f2f2eac7b4e3a6a
|
4
|
+
data.tar.gz: e0539d88ae84a348113be06e9f81299b64a5c74598428e9d2a33ec00b0b49584
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a40c8720b6e44da21bb5c2d0247055cf8b4f7d04e5b6d9e5d6db03778af368e64b5d2ba375aa02aa671aebdbf3d64d87c8090beb958bb7dc83ef5b1da52196e
|
7
|
+
data.tar.gz: 77bac482b0518e59b534d1916b9155c2a59e75308cc71e93354c9a3d071fa10c35362ee836f3eb456b15f612b73d503f06c735d7af942d220415a39c6943c606
|
data/README.md
CHANGED
@@ -24,6 +24,7 @@ without having to code your own oAuth process, API requests, and pagination.
|
|
24
24
|
* [Organizations](#organizations)
|
25
25
|
* [Assets](#assets)
|
26
26
|
* [Asset Locations](#asset-locations)
|
27
|
+
* [Fields](#fields)
|
27
28
|
* [Direct API Requests](#direct-api-requests)
|
28
29
|
* [GET](#get)
|
29
30
|
* [POST](#post)
|
@@ -346,7 +347,7 @@ organization.links
|
|
346
347
|
# => {
|
347
348
|
# 'self' => 'https://sandboxapi.deere.com/platform/organizations/1234',
|
348
349
|
# 'machines' => 'https://sandboxapi.deere.com/platform/organizations/1234/machines',
|
349
|
-
# 'wdtCapableMachines' => '
|
350
|
+
# 'wdtCapableMachines' => 'https://sandboxapi.deere.com/platform/organizations/1234/machines?capability=wdt'
|
350
351
|
# }
|
351
352
|
|
352
353
|
organization.assets
|
@@ -558,6 +559,115 @@ Note that locations are called "Asset Locations" in John Deere, but we call the
|
|
558
559
|
`asset.locations`, for brevity.
|
559
560
|
|
560
561
|
|
562
|
+
### [Fields](https://developer.deere.com/#!documentation&doc=myjohndeere%2FfieldsADS.htm)
|
563
|
+
|
564
|
+
Handles an organization's fields. Field collections support the following methods:
|
565
|
+
|
566
|
+
* all
|
567
|
+
* count
|
568
|
+
* first
|
569
|
+
* find(field\_id)
|
570
|
+
|
571
|
+
An individual field supports the following methods and associations:
|
572
|
+
|
573
|
+
* id
|
574
|
+
* name
|
575
|
+
* archived?
|
576
|
+
* links
|
577
|
+
* flags (collection of this field's flags)
|
578
|
+
|
579
|
+
The `count` method only requires loading the first page of results, so it's a relatively cheap call. On the other hand,
|
580
|
+
`all` forces the entire collection to be loaded from John Deere's API, so use with caution. Fields can be
|
581
|
+
created via the API, but there is no `create` method on this collection yet.
|
582
|
+
|
583
|
+
```ruby
|
584
|
+
organization.fields
|
585
|
+
# => collection of fields under this organization
|
586
|
+
|
587
|
+
organization.fields.count
|
588
|
+
# => 15
|
589
|
+
|
590
|
+
organization.fields.first
|
591
|
+
# => an individual field object
|
592
|
+
|
593
|
+
field = organization.fields.find(1234)
|
594
|
+
# => an individual field object, fetched by ID
|
595
|
+
|
596
|
+
field.name
|
597
|
+
# => 'Smith Field'
|
598
|
+
|
599
|
+
field.archived?
|
600
|
+
# => false
|
601
|
+
|
602
|
+
field.links
|
603
|
+
# => a hash of API urls related to this field
|
604
|
+
|
605
|
+
field.flags
|
606
|
+
# => collection of flags belonging to this field
|
607
|
+
```
|
608
|
+
|
609
|
+
|
610
|
+
### [Flags](https://developer.deere.com/#!documentation&doc=.%2Fmyjohndeere%2Fflags.htm)
|
611
|
+
|
612
|
+
Handles a field's flags. Flag collections support the following methods. Note, John Deere does not provide an endpoint to retrieve a specific flag by id:
|
613
|
+
|
614
|
+
* all
|
615
|
+
* count
|
616
|
+
* first
|
617
|
+
|
618
|
+
An individual flag supports the following methods and associations:
|
619
|
+
|
620
|
+
* id
|
621
|
+
* notes
|
622
|
+
* geometry
|
623
|
+
* archived?
|
624
|
+
* proximity\_alert\_enabled?
|
625
|
+
* links
|
626
|
+
|
627
|
+
The `count` method only requires loading the first page of results, so it's a relatively cheap call. On the other hand,
|
628
|
+
`all` forces the entire collection to be loaded from John Deere's API, so use with caution. Flags can be
|
629
|
+
created via the API, but there is no `create` method on this collection yet.
|
630
|
+
|
631
|
+
```ruby
|
632
|
+
field.flags
|
633
|
+
# => collection of flags under this field
|
634
|
+
|
635
|
+
field.flags.count
|
636
|
+
# => 15
|
637
|
+
|
638
|
+
flag = field.flags.first
|
639
|
+
# => an individual flag object
|
640
|
+
|
641
|
+
flag.notes
|
642
|
+
# => 'A big rock on the left after entering the field'
|
643
|
+
|
644
|
+
flag.geometry
|
645
|
+
# => a GeoJSON formatted hash, for example:
|
646
|
+
# {
|
647
|
+
# "type"=>"Feature",
|
648
|
+
# "geometry"=>{
|
649
|
+
# "geometries"=>[
|
650
|
+
# {
|
651
|
+
# "coordinates"=>[-95.123456, 40.123456],
|
652
|
+
# "type"=>"Point"
|
653
|
+
# }
|
654
|
+
# ],
|
655
|
+
# "type"=>"GeometryCollection"
|
656
|
+
# }
|
657
|
+
# }
|
658
|
+
|
659
|
+
|
660
|
+
field.archived?
|
661
|
+
# => false
|
662
|
+
|
663
|
+
field.proximity_alert_enabled?
|
664
|
+
# => true
|
665
|
+
|
666
|
+
field.links
|
667
|
+
# => a hash of API urls related to this flag
|
668
|
+
```
|
669
|
+
|
670
|
+
|
561
671
|
## Direct API Requests
|
562
672
|
|
563
673
|
While the goal of the client is to eliminate the need to make/interpret calls to the John Deere API, it's important
|
data/lib/my_john_deere_api.rb
CHANGED
@@ -4,10 +4,11 @@ module MyJohnDeereApi
|
|
4
4
|
include Helpers::CaseConversion
|
5
5
|
|
6
6
|
attr_accessor :contribution_definition_id
|
7
|
-
attr_reader :api_key, :api_secret, :access_token, :access_secret
|
7
|
+
attr_reader :api_key, :api_secret, :access_token, :access_secret, :http_retry_options
|
8
8
|
|
9
9
|
DEFAULTS = {
|
10
|
-
environment: :live
|
10
|
+
environment: :live,
|
11
|
+
http_retry: {}
|
11
12
|
}
|
12
13
|
|
13
14
|
##
|
@@ -37,6 +38,7 @@ module MyJohnDeereApi
|
|
37
38
|
|
38
39
|
self.environment = options[:environment]
|
39
40
|
@contribution_definition_id = options[:contribution_definition_id]
|
41
|
+
@http_retry_options = options[:http_retry]
|
40
42
|
end
|
41
43
|
|
42
44
|
##
|
@@ -45,7 +47,15 @@ module MyJohnDeereApi
|
|
45
47
|
|
46
48
|
def accessor
|
47
49
|
return @accessor if defined?(@accessor)
|
48
|
-
|
50
|
+
|
51
|
+
@accessor = NetHttpRetry::Decorator.new(
|
52
|
+
OAuth::AccessToken.new(
|
53
|
+
consumer.user_get,
|
54
|
+
access_token,
|
55
|
+
access_secret
|
56
|
+
),
|
57
|
+
http_retry_options
|
58
|
+
)
|
49
59
|
end
|
50
60
|
|
51
61
|
##
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module MyJohnDeereApi
|
2
|
+
module NetHttpRetry
|
3
|
+
class Decorator
|
4
|
+
attr_reader :object, :request_methods, :retry_delay_exponent, :max_retries, :retry_codes, :valid_codes
|
5
|
+
|
6
|
+
DEFAULTS = {
|
7
|
+
request_methods: [:get, :post, :put, :delete],
|
8
|
+
retry_delay_exponent: 2,
|
9
|
+
max_retries: 12,
|
10
|
+
retry_codes: ['429', '503'],
|
11
|
+
valid_codes: ['200', '201', '204'],
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize(object, options={})
|
15
|
+
@object = object
|
16
|
+
|
17
|
+
# defaults that can be used as-is
|
18
|
+
[:request_methods, :retry_delay_exponent, :max_retries].each do |option|
|
19
|
+
instance_variable_set(:"@#{option}", options[option] || DEFAULTS[option])
|
20
|
+
end
|
21
|
+
|
22
|
+
# defaults that require casting as string arrays
|
23
|
+
[:retry_codes, :valid_codes].each do |option|
|
24
|
+
instance_variable_set(:"@#{option}", (options[option] || DEFAULTS[option]).map(&:to_s))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def request(method_name, *args)
|
29
|
+
retries = 0
|
30
|
+
result = object.send(method_name, *args)
|
31
|
+
|
32
|
+
while retry_codes.include?(result.code)
|
33
|
+
if retries >= max_retries
|
34
|
+
raise MaxRetriesExceededError.new(method_name, "#{result.code} #{result.message}")
|
35
|
+
end
|
36
|
+
|
37
|
+
delay = [result['retry-after'].to_i, retry_delay_exponent ** retries].max
|
38
|
+
sleep(delay)
|
39
|
+
|
40
|
+
result = object.send(method_name, *args)
|
41
|
+
retries += 1
|
42
|
+
end
|
43
|
+
|
44
|
+
unless valid_codes.include?(result.code)
|
45
|
+
raise InvalidResponseError.new(result)
|
46
|
+
end
|
47
|
+
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def method_missing(method_name, *args, &block)
|
54
|
+
if request_methods.include?(method_name)
|
55
|
+
request(method_name, *args)
|
56
|
+
else
|
57
|
+
object.send(method_name, *args, &block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MyJohnDeereApi
|
2
|
+
module NetHttpRetry
|
3
|
+
##
|
4
|
+
# This error is used when a single request has exceeded
|
5
|
+
# the number of retries allowed by NetHttpRetry::Decorator::MAX_RETRIES.
|
6
|
+
|
7
|
+
class InvalidResponseError < StandardError
|
8
|
+
|
9
|
+
##
|
10
|
+
# argument is a string which describes the attempted request
|
11
|
+
|
12
|
+
def initialize(response)
|
13
|
+
message = {
|
14
|
+
code: response.code,
|
15
|
+
message: response.message,
|
16
|
+
body: response.body,
|
17
|
+
}.to_json
|
18
|
+
|
19
|
+
super(message)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module MyJohnDeereApi
|
2
|
+
module NetHttpRetry
|
3
|
+
##
|
4
|
+
# This error is used when a single request has exceeded
|
5
|
+
# the number of retries allowed by NetHttpRetry::Decorator::MAX_RETRIES.
|
6
|
+
|
7
|
+
class MaxRetriesExceededError < StandardError
|
8
|
+
|
9
|
+
##
|
10
|
+
# argument is a string which describes the attempted request
|
11
|
+
|
12
|
+
def initialize(request_method, response_message)
|
13
|
+
message = "Max retries exceeded for #{request_method.to_s.upcase} " +
|
14
|
+
"request: #{response_message}"
|
15
|
+
|
16
|
+
super(message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -6,7 +6,7 @@ describe 'MyJohnDeereApi::Client' do
|
|
6
6
|
assert_equal 'thisIsATest', client.send(:camelize, :this_is_a_test)
|
7
7
|
end
|
8
8
|
|
9
|
-
describe '#initialize(api_key, api_secret)' do
|
9
|
+
describe '#initialize(api_key, api_secret, options={})' do
|
10
10
|
it 'sets the api key/secret' do
|
11
11
|
client = JD::Client.new(api_key, api_secret)
|
12
12
|
|
@@ -35,6 +35,23 @@ describe 'MyJohnDeereApi::Client' do
|
|
35
35
|
client = JD::Client.new(api_key, api_secret, contribution_definition_id: contribution_definition_id)
|
36
36
|
assert_equal contribution_definition_id, client.contribution_definition_id
|
37
37
|
end
|
38
|
+
|
39
|
+
it 'accepts a list of parameters for NetHttpRetry' do
|
40
|
+
custom_retries = JD::NetHttpRetry::Decorator::DEFAULTS[:max_retries] + 10
|
41
|
+
|
42
|
+
VCR.use_cassette('catalog') do
|
43
|
+
new_client = JD::Client.new(
|
44
|
+
api_key,
|
45
|
+
api_secret,
|
46
|
+
contribution_definition_id: contribution_definition_id,
|
47
|
+
environment: :sandbox,
|
48
|
+
access: [access_token, access_secret],
|
49
|
+
http_retry: {max_retries: custom_retries}
|
50
|
+
)
|
51
|
+
|
52
|
+
assert_equal custom_retries, new_client.accessor.max_retries
|
53
|
+
end
|
54
|
+
end
|
38
55
|
end
|
39
56
|
|
40
57
|
describe '#contribution_definition_id' do
|
@@ -130,7 +147,7 @@ describe 'MyJohnDeereApi::Client' do
|
|
130
147
|
|
131
148
|
it 'sends the request' do
|
132
149
|
response = VCR.use_cassette('put_asset') { client.put("/assets/#{asset_id}", attributes) }
|
133
|
-
|
150
|
+
|
134
151
|
assert_equal '204', response.code
|
135
152
|
assert_equal 'No Content', response.message
|
136
153
|
end
|
@@ -199,7 +216,7 @@ describe 'MyJohnDeereApi::Client' do
|
|
199
216
|
|
200
217
|
describe '#accessor' do
|
201
218
|
it 'returns an object that can make user-specific requests' do
|
202
|
-
assert_kind_of
|
219
|
+
assert_kind_of JD::NetHttpRetry::Decorator, accessor
|
203
220
|
assert_kind_of OAuth::Consumer, accessor.consumer
|
204
221
|
assert_equal access_token, accessor.token
|
205
222
|
assert_equal access_secret, accessor.secret
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'support/helper'
|
2
|
+
|
3
|
+
describe 'JD::NetHttpRetry::Decorator' do
|
4
|
+
REQUESTS = {
|
5
|
+
get: '/',
|
6
|
+
post: '/organizations/000000/assets',
|
7
|
+
put: '/assets/00000000-0000-0000-0000-000000000000',
|
8
|
+
delete: '/assets/00000000-0000-0000-0000-000000000000'
|
9
|
+
}
|
10
|
+
|
11
|
+
REQUEST_METHODS = REQUESTS.keys
|
12
|
+
|
13
|
+
let(:klass) { JD::NetHttpRetry::Decorator }
|
14
|
+
let(:object) { klass.new(mock, options) }
|
15
|
+
|
16
|
+
let(:options) do
|
17
|
+
{
|
18
|
+
request_methods: request_methods,
|
19
|
+
retry_delay_exponent: retry_delay_exponent,
|
20
|
+
max_retries: max_retries,
|
21
|
+
retry_codes: retry_codes,
|
22
|
+
valid_codes: valid_codes
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:request_methods) { nil }
|
27
|
+
let(:retry_delay_exponent) { nil }
|
28
|
+
let(:max_retries) { nil }
|
29
|
+
let(:retry_codes) { nil }
|
30
|
+
let(:valid_codes) { nil }
|
31
|
+
|
32
|
+
let(:retry_values) { [13, 17, 19, 23] }
|
33
|
+
let(:exponential_retries) { (0..klass::DEFAULTS[:max_retries]-1).map{|i| 2 ** i} }
|
34
|
+
|
35
|
+
it 'wraps a "net-http"-responsive object' do
|
36
|
+
assert_kind_of OAuth::AccessToken, accessor.object
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#initialize' do
|
40
|
+
describe 'when request methods are specified' do
|
41
|
+
let(:request_methods) { [:banana, :fana, :fofana] }
|
42
|
+
|
43
|
+
it 'uses the supplied values' do
|
44
|
+
assert_equal request_methods, object.request_methods
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'when request methods are not specified' do
|
49
|
+
it 'uses the default values' do
|
50
|
+
assert_equal klass::DEFAULTS[:request_methods], object.request_methods
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'when retry_delay_exponent is specified' do
|
55
|
+
let(:retry_delay_exponent) { 42 }
|
56
|
+
|
57
|
+
it 'uses the supplied value' do
|
58
|
+
assert_equal retry_delay_exponent, object.retry_delay_exponent
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'when retry_delay_exponent is not specified' do
|
63
|
+
it 'uses the default value' do
|
64
|
+
assert_equal klass::DEFAULTS[:retry_delay_exponent], object.retry_delay_exponent
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'when max_retries is specified' do
|
69
|
+
let(:max_retries) { 42 }
|
70
|
+
|
71
|
+
it 'uses the supplied value' do
|
72
|
+
assert_equal max_retries, object.max_retries
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'when max_retries is not specified' do
|
77
|
+
it 'uses the default value' do
|
78
|
+
assert_equal klass::DEFAULTS[:max_retries], object.max_retries
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'when retry_codes are specified' do
|
83
|
+
let(:retry_codes) { ['200', '201'] }
|
84
|
+
|
85
|
+
it 'uses the supplied values' do
|
86
|
+
assert_equal retry_codes, object.retry_codes
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe 'when retry_codes are specified as integers' do
|
91
|
+
let(:retry_codes) { [200, 201] }
|
92
|
+
|
93
|
+
it 'uses the stringified versions of the supplied values' do
|
94
|
+
assert_equal retry_codes.map(&:to_s), object.retry_codes
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe 'when retry_codes are not specified' do
|
99
|
+
it 'uses the default values' do
|
100
|
+
assert_equal klass::DEFAULTS[:retry_codes], object.retry_codes
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'when valid_codes are specified' do
|
105
|
+
let(:valid_codes) { ['123', '234'] }
|
106
|
+
|
107
|
+
it 'uses the supplied values' do
|
108
|
+
assert_equal valid_codes, object.valid_codes
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe 'when valid_codes are specified as integers' do
|
113
|
+
let(:valid_codes) { [123, 234] }
|
114
|
+
|
115
|
+
it 'uses the stringified versions of the supplied values' do
|
116
|
+
assert_equal valid_codes.map(&:to_s), object.valid_codes
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'when valid_codes are not specified' do
|
121
|
+
it 'uses the default values' do
|
122
|
+
assert_kind_of Array, klass::DEFAULTS[:valid_codes]
|
123
|
+
refute klass::DEFAULTS[:valid_codes].empty?
|
124
|
+
|
125
|
+
assert_equal klass::DEFAULTS[:valid_codes], object.valid_codes
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "honors Retry-After headers" do
|
131
|
+
REQUEST_METHODS.each do |request_method|
|
132
|
+
it "in #{request_method.to_s.upcase} requests" do
|
133
|
+
retry_values.each do |retry_seconds|
|
134
|
+
accessor.expects(:sleep).with(retry_seconds)
|
135
|
+
end
|
136
|
+
|
137
|
+
VCR.use_cassette("accessor/#{request_method}_retry") do
|
138
|
+
accessor.send(request_method, REQUESTS[request_method])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'employs exponential wait times for automatic retries' do
|
145
|
+
REQUEST_METHODS.each do |request_method|
|
146
|
+
it "in #{request_method.to_s.upcase} requests" do
|
147
|
+
exponential_retries[0,8].each do |retry_seconds|
|
148
|
+
accessor.expects(:sleep).with(retry_seconds)
|
149
|
+
end
|
150
|
+
|
151
|
+
VCR.use_cassette("accessor/#{request_method}_failed") do
|
152
|
+
accessor.send(request_method, REQUESTS[request_method])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe 'when Retry-After is shorter than exponential wait time' do
|
159
|
+
REQUEST_METHODS.each do |request_method|
|
160
|
+
it "chooses longer exponential time in #{request_method.to_s.upcase} requests" do
|
161
|
+
exponential_retries[0,4].each do |retry_seconds|
|
162
|
+
accessor.expects(:sleep).with(retry_seconds)
|
163
|
+
end
|
164
|
+
|
165
|
+
VCR.use_cassette("accessor/#{request_method}_retry_too_soon") do
|
166
|
+
accessor.send(request_method, REQUESTS[request_method])
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe 'when max retries have been reached' do
|
173
|
+
REQUEST_METHODS.each do |request_method|
|
174
|
+
it "returns an error for #{request_method.to_s.upcase} requests" do
|
175
|
+
exponential_retries.each do |retry_seconds|
|
176
|
+
accessor.expects(:sleep).with(retry_seconds)
|
177
|
+
end
|
178
|
+
|
179
|
+
exception = assert_raises(JD::NetHttpRetry::MaxRetriesExceededError) do
|
180
|
+
VCR.use_cassette("accessor/#{request_method}_max_failed") do
|
181
|
+
accessor.send(request_method, REQUESTS[request_method])
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
expected_error = "Max retries exceeded for #{request_method.to_s.upcase} request: 429 Too Many Requests"
|
186
|
+
assert_equal expected_error, exception.message
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe 'when an invalid response code is returned' do
|
192
|
+
REQUEST_METHODS.each do |request_method|
|
193
|
+
it "returns an error for #{request_method.to_s.upcase} requests" do
|
194
|
+
exception = assert_raises(JD::NetHttpRetry::InvalidResponseError) do
|
195
|
+
VCR.use_cassette("accessor/#{request_method}_invalid") do
|
196
|
+
accessor.send(request_method, REQUESTS[request_method])
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
exception_json = JSON.parse(exception.message)
|
201
|
+
|
202
|
+
assert_equal '500', exception_json['code']
|
203
|
+
assert_equal 'Internal Error', exception_json['message']
|
204
|
+
assert_equal 'You Have Died of Dysentery', exception_json['body']
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|