Skip to content

Commit ba9b2da

Browse files
committed
add test to display retries
1 parent 3192c83 commit ba9b2da

File tree

6 files changed

+79
-59
lines changed

6 files changed

+79
-59
lines changed

.solargraph.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
22
include:
3-
- "**/*.rb"
3+
- "**/*.rb"
44
exclude:
5-
- vendor/**/*
6-
- ".bundle/**/*"
7-
- "bin/**/*"
5+
- vendor/**/*
6+
- ".bundle/**/*"
7+
- "bin/**/*"
88
require: []
99
domains: []
1010
reporters:
11-
- rubocop
12-
- require_not_found
11+
- rubocop
12+
- require_not_found
1313
formatter:
1414
rubocop:
1515
cops: safe

Gemfile.lock

+19-15
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,26 @@ GEM
1818
coderay (1.1.3)
1919
crack (0.4.5)
2020
rexml
21-
docile (1.3.5)
21+
docile (1.4.0)
2222
e2mmap (0.1.0)
23-
faraday (1.4.1)
23+
faraday (1.4.2)
24+
faraday-em_http (~> 1.0)
25+
faraday-em_synchrony (~> 1.0)
2426
faraday-excon (~> 1.1)
2527
faraday-net_http (~> 1.0)
2628
faraday-net_http_persistent (~> 1.1)
2729
multipart-post (>= 1.2, < 3)
2830
ruby2_keywords (>= 0.0.4)
31+
faraday-em_http (1.0.0)
32+
faraday-em_synchrony (1.0.0)
2933
faraday-excon (1.1.0)
3034
faraday-net_http (1.0.1)
3135
faraday-net_http_persistent (1.1.0)
3236
faraday_middleware (1.0.0)
3337
faraday (~> 1.0)
34-
ffi (1.15.0)
38+
ffi (1.15.1)
3539
formatador (0.2.5)
36-
guard (2.16.2)
40+
guard (2.17.0)
3741
formatador (>= 0.2.4)
3842
listen (>= 2.7, < 4.0)
3943
lumberjack (>= 1.0.12, < 2.0)
@@ -57,11 +61,11 @@ GEM
5761
rb-inotify (~> 0.9, >= 0.9.10)
5862
lumberjack (1.2.8)
5963
method_source (1.0.0)
60-
mini_portile2 (2.5.0)
64+
mini_portile2 (2.5.1)
6165
minitest (5.14.4)
6266
minitest-fail-fast (0.1.0)
6367
minitest (~> 5)
64-
minitest-focus (1.2.1)
68+
minitest-focus (1.3.1)
6569
minitest (>= 4, < 6)
6670
minitest-hooks (1.5.0)
6771
minitest (> 5.3)
@@ -74,14 +78,14 @@ GEM
7478
ruby-progressbar
7579
multipart-post (2.1.1)
7680
nenv (0.3.0)
77-
nokogiri (1.11.3)
81+
nokogiri (1.11.6)
7882
mini_portile2 (~> 2.5.0)
7983
racc (~> 1.4)
8084
notiffany (0.1.3)
8185
nenv (~> 0.1)
8286
shellany (~> 0.0)
8387
parallel (1.20.1)
84-
parser (3.0.1.0)
88+
parser (3.0.1.1)
8589
ast (~> 2.4.1)
8690
pry (0.14.1)
8791
coderay (~> 1.1)
@@ -90,24 +94,24 @@ GEM
9094
racc (1.5.2)
9195
rainbow (3.0.0)
9296
rake (13.0.3)
93-
rb-fsevent (0.10.4)
97+
rb-fsevent (0.11.0)
9498
rb-inotify (0.10.1)
9599
ffi (~> 1.0)
96100
regexp_parser (2.1.1)
97101
reverse_markdown (2.0.0)
98102
nokogiri
99103
rexml (3.2.5)
100-
rubocop (1.13.0)
104+
rubocop (1.15.0)
101105
parallel (~> 1.10)
102106
parser (>= 3.0.0.0)
103107
rainbow (>= 2.2.2, < 4.0)
104108
regexp_parser (>= 1.8, < 3.0)
105109
rexml
106-
rubocop-ast (>= 1.2.0, < 2.0)
110+
rubocop-ast (>= 1.5.0, < 2.0)
107111
ruby-progressbar (~> 1.7)
108112
unicode-display_width (>= 1.4.0, < 3.0)
109-
rubocop-ast (1.4.1)
110-
parser (>= 2.7.1.5)
113+
rubocop-ast (1.6.0)
114+
parser (>= 3.0.1.1)
111115
ruby-progressbar (1.11.0)
112116
ruby2_keywords (0.0.4)
113117
shellany (0.0.1)
@@ -116,7 +120,7 @@ GEM
116120
simplecov-html (~> 0.11)
117121
simplecov_json_formatter (~> 0.1)
118122
simplecov-html (0.12.3)
119-
simplecov_json_formatter (0.1.2)
123+
simplecov_json_formatter (0.1.3)
120124
solargraph (0.40.4)
121125
backport (~> 1.1)
122126
benchmark
@@ -134,7 +138,7 @@ GEM
134138
thor (1.1.0)
135139
tilt (2.0.10)
136140
unicode-display_width (2.0.0)
137-
webmock (3.12.2)
141+
webmock (3.13.0)
138142
addressable (>= 2.3.6)
139143
crack (>= 0.3.2)
140144
hashdiff (>= 0.4.0, < 2.0.0)

lib/shipengine/exceptions.rb

+13-7
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,26 @@ def initialize(message:, code:, request_id: nil, source: nil)
6868
end
6969

7070
class RateLimitError < SystemError
71-
attr_reader :retry_attempt
72-
73-
def initialize(message: 'You have exceeded the rate limit.', source: nil, request_id: nil)
74-
super(message: message, code: ErrorCode.get(:RATE_LIMIT_EXCEEDED), request_id: request_id, source: source)
71+
attr_reader :retries
72+
73+
def initialize(retries: nil, message: 'You have exceeded the rate limit.', source: nil, request_id: nil)
74+
super(
75+
message: message,
76+
code: ErrorCode.get(:RATE_LIMIT_EXCEEDED),
77+
request_id: request_id,
78+
source: source,
79+
)
80+
@retries = retries
7581
end
7682
end
7783

7884
class TimeoutError < SystemError
79-
def initialize(message:, source: nil, request_id: nil)
85+
def initialize(message:, source: ::Exceptions::DEFAULT_SOURCE, request_id: nil)
8086
super(message: message, code: ErrorCode.get(:TIMEOUT), request_id: request_id, source: source)
8187
end
8288
end
8389

84-
def self.create_error_instance_by_type(type:, message:, code:, request_id: nil, source: nil)
90+
def self.create_error_instance(type:, message:, code:, request_id: nil, source: nil, config: nil)
8591
case type
8692
when Exceptions::ErrorType.get(:BUSINESS_RULES)
8793
BusinessRulesError.new(message: message, code: code, request_id: request_id, source: source)
@@ -94,7 +100,7 @@ def self.create_error_instance_by_type(type:, message:, code:, request_id: nil,
94100
when Exceptions::ErrorType.get(:SYSTEM)
95101
case code
96102
when ErrorCode.get(:RATE_LIMIT_EXCEEDED)
97-
RateLimitError.new(message: message, request_id: request_id, source: source)
103+
RateLimitError.new(message: message, request_id: request_id, source: source, retries: config.retries)
98104
when ErrorCode.get(:TIMEOUT)
99105
TimeoutError.new(message: message, request_id: request_id, source: source)
100106
else

lib/shipengine/internal_client.rb

+18-19
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ module CustomMiddleware
2020
# This transforms the `retryAfter` field from our JSON-RPC server to an HTTP header, so this client
2121
# can use the standard retry middleware from faraday-middleware.
2222
class RetryAfter < Faraday::Middleware
23+
attr_reader :retry_attempt
24+
2325
def initialize(app)
2426
super(app)
2527
@retry_attempt = 0
@@ -32,15 +34,11 @@ def on_complete(env)
3234
return env unless (status == 429) && body.is_a?(Hash) && body['error']
3335

3436
# ShipEngineErrorLogger.log('Retrying...attempt: #{ @retry_attempt}')
35-
env[:response_headers]['Retry-After'] = body.dig('error', 'data', 'retryAfter').to_s unless env[:response_headers]['Retry-After']
37+
env[:response_headers]['Retry-After'] ||= body.dig('error', 'data', 'retryAfter').to_s
3638
@retry_attempt += 1
3739
env
3840
end
3941
end
40-
41-
def self.register_request_middleware
42-
Faraday::Request.register_middleware(retry_after: RetryAfter)
43-
end
4442
end
4543

4644
class InternalClientResponseSuccess
@@ -59,7 +57,7 @@ class InternalClient
5957

6058
# @param [::ShipEngine::Configuration] configuration
6159
def initialize(configuration)
62-
CustomMiddleware.register_request_middleware
60+
Faraday::Request.register_middleware(retry_after: CustomMiddleware::RetryAfter)
6361
@configuration = configuration
6462
end
6563

@@ -83,20 +81,21 @@ def make_request(method, params, config = { api_key: nil, base_url: nil, retries
8381
req.body = build_jsonrpc_request_body(method, params)
8482
end
8583

86-
assert_shipengine_rpc_success(response)
84+
assert_shipengine_rpc_success(response, config_with_overrides)
85+
8786
result, id = response.body.values_at('result', 'id')
8887
InternalClientResponseSuccess.new(result: result, request_id: id)
8988
end
9089

9190
private
9291

93-
# @param [::ShipEngine::Configuration] configuration
92+
# @param config [::ShipEngine::Configuration]
9493
# @return [::Faraday::Connection]
95-
def create_connection(configuration)
96-
retries = configuration.retries
97-
base_url = configuration.base_url
98-
api_key = configuration.api_key
99-
timeout = configuration.timeout
94+
def create_connection(config)
95+
retries = config.retries
96+
base_url = config.base_url
97+
api_key = config.api_key
98+
timeout = config.timeout
10099
Faraday.new(url: base_url, request: { timeout: timeout }) do |f|
101100
f.request :json
102101
f.request :retry, {
@@ -111,13 +110,12 @@ def create_connection(configuration)
111110
'Accept' => 'application/json',
112111
'User-Agent' => "shipengine-ruby/#{VERSION} (#{RUBY_PLATFORM})"
113112
}
114-
115113
f.response :json
116114
end
117115
end
118116

119-
# @param [String] method - e.g. "address.validate.v1"
120-
# @param [Hash] params
117+
# @param method [String] e.g. "address.validate.v1"
118+
# @param params [Hash]
121119
# @return [Hash] - JSON:RPC response
122120
def build_jsonrpc_request_body(method, params)
123121
{
@@ -128,9 +126,10 @@ def build_jsonrpc_request_body(method, params)
128126
}
129127
end
130128

131-
def assert_shipengine_rpc_success(response)
129+
# @param response [::Faraday::Response]
130+
# @param config [::ShipEngine::Configuration]
131+
def assert_shipengine_rpc_success(response, config)
132132
body = response.body
133-
134133
unless body.is_a?(Hash)
135134
# this should not happen
136135
ShipEngineErrorLogger.log('response body is NOT a hash', [status: response.status, body: response.body])
@@ -142,7 +141,7 @@ def assert_shipengine_rpc_success(response)
142141

143142
message, data = error.values_at('message', 'data')
144143
source, type, code = data.values_at('source', 'type', 'code')
145-
raise Exceptions.create_error_instance_by_type(type: type, message: message, code: code, request_id: request_id, source: source)
144+
raise Exceptions.create_error_instance(type: type, message: message, code: code, request_id: request_id, source: source, config: config)
146145
end
147146
end
148147
end

test/internal_client_test.rb

+16-6
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,24 @@ def self.rate_limit_error(data: {})
224224
assert_raises_rate_limit_error { client.validate_address(valid_address_params) }
225225
end
226226

227+
it 'should throw an error with the number of tries' do
228+
client = ShipEngine::Client.new(api_key: 'abc123', retries: 2)
229+
stub_request(:post, base_url)
230+
.to_return(status: 429, body: Factory.rate_limit_error).then
231+
.to_return(status: 429, body: Factory.rate_limit_error).then
232+
.to_return(status: 429, body: Factory.rate_limit_error)
233+
assert_raises_rate_limit_error(retries: 2) { client.validate_address(valid_address_params) }
234+
end
235+
227236
it 'respects the Retry-After header, which can override error.retryAfter' do
228237
client = ShipEngine::Client.new(api_key: 'abc123', retries: 1)
229238
stub = stub_request(:post, base_url)
230-
.to_return(status: 429, body: Factory.rate_limit_error, headers: { "Retry-After": 1 }).then
231-
.to_return(status: 200, body: valid_address_res)
239+
.to_return(status: 429, body: Factory.rate_limit_error, headers: { "Retry-After": 1 })
240+
.then.to_return(status: 200, body: valid_address_res)
232241
start = Time.now
233242
client.validate_address(valid_address_params)
234243
diff = Time.now - start
244+
235245
assert_operator(diff, :>, 1, 'should take more than than 1 second')
236246
assert_requested(stub, times: 2)
237247
end
@@ -263,17 +273,17 @@ def self.rate_limit_error(data: {})
263273

264274
# https://auctane.atlassian.net/browse/DX-1500
265275
it 'SLOW: should use the retryAfter field in errors to dictate how long it should wait to retry' do
266-
retries = 2
276+
retries = 3
267277
client = ShipEngine::Client.new(api_key: 'abc123', retries: retries)
268278
stub = stub_request(:post, base_url)
269279
.to_return(status: 429, body: Factory.rate_limit_error(data: { retryAfter: 1 })).then
270-
.to_return(status: 429, body: Factory.rate_limit_error(data: { retryAfter: 2 })).then
280+
.to_return(status: 429, body: Factory.rate_limit_error(data: { retryAfter: 1 })).then
281+
.to_return(status: 429, body: Factory.rate_limit_error(data: { retryAfter: 1 })).then
271282
.to_return(status: 200, body: valid_address_res)
272283
start = Time.now
273284
client.validate_address(valid_address_params)
274285
diff = Time.now - start
275-
assert_operator(diff, :>, 3, 'should take between 3 and 4 seconds')
276-
assert_operator(diff, :<, 4, 'should take between 3 and 4 seconds')
286+
assert(diff > 3 && diff < 4, 'should take between 3 and 4 seconds')
277287
assert_requested(stub, times: retries + 1)
278288
end
279289
end

test/test_utility/custom_assertions.rb

+7-6
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,13 @@ def assert_address_validation_result(expected_result, response_result)
7070
# rubocop:enable Layout/LineLength
7171
end
7272

73-
def assert_raises_rate_limit_error(&block)
74-
assert_raises_shipengine(ShipEngine::Exceptions::RateLimitError, {
75-
code: 'rate_limit_exceeded',
76-
message: 'You have exceeded the rate limit.',
77-
source: 'shipengine'
78-
}, &block)
73+
def assert_raises_rate_limit_error(retries: nil, &block)
74+
err = assert_raises_shipengine(ShipEngine::Exceptions::RateLimitError, {
75+
code: 'rate_limit_exceeded',
76+
message: 'You have exceeded the rate limit.',
77+
source: 'shipengine'
78+
}, &block)
79+
assert_equal(retries, err.retries) unless retries.nil?
7980
end
8081

8182
# @param expected_messages [Array<Hash>]

0 commit comments

Comments
 (0)