http 5.3.1 → 6.0.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/CHANGELOG.md +241 -41
- data/LICENSE.txt +1 -1
- data/README.md +110 -13
- data/UPGRADING.md +491 -0
- data/http.gemspec +32 -29
- data/lib/http/base64.rb +11 -1
- data/lib/http/chainable/helpers.rb +62 -0
- data/lib/http/chainable/verbs.rb +136 -0
- data/lib/http/chainable.rb +232 -136
- data/lib/http/client.rb +158 -127
- data/lib/http/connection/internals.rb +141 -0
- data/lib/http/connection.rb +126 -97
- data/lib/http/content_type.rb +61 -6
- data/lib/http/errors.rb +25 -1
- data/lib/http/feature.rb +65 -5
- data/lib/http/features/auto_deflate.rb +124 -17
- data/lib/http/features/auto_inflate.rb +38 -15
- data/lib/http/features/caching/entry.rb +178 -0
- data/lib/http/features/caching/in_memory_store.rb +63 -0
- data/lib/http/features/caching.rb +216 -0
- data/lib/http/features/digest_auth.rb +234 -0
- data/lib/http/features/instrumentation.rb +97 -17
- data/lib/http/features/logging.rb +183 -5
- data/lib/http/features/normalize_uri.rb +17 -0
- data/lib/http/features/raise_error.rb +18 -3
- data/lib/http/form_data/composite_io.rb +106 -0
- data/lib/http/form_data/file.rb +95 -0
- data/lib/http/form_data/multipart/param.rb +62 -0
- data/lib/http/form_data/multipart.rb +106 -0
- data/lib/http/form_data/part.rb +52 -0
- data/lib/http/form_data/readable.rb +58 -0
- data/lib/http/form_data/urlencoded.rb +175 -0
- data/lib/http/form_data/version.rb +8 -0
- data/lib/http/form_data.rb +102 -0
- data/lib/http/headers/known.rb +3 -0
- data/lib/http/headers/normalizer.rb +17 -36
- data/lib/http/headers.rb +172 -65
- data/lib/http/mime_type/adapter.rb +24 -9
- data/lib/http/mime_type/json.rb +19 -4
- data/lib/http/mime_type.rb +21 -3
- data/lib/http/options/definitions.rb +189 -0
- data/lib/http/options.rb +172 -125
- data/lib/http/redirector.rb +80 -75
- data/lib/http/request/body.rb +87 -6
- data/lib/http/request/builder.rb +184 -0
- data/lib/http/request/proxy.rb +83 -0
- data/lib/http/request/writer.rb +76 -16
- data/lib/http/request.rb +214 -98
- data/lib/http/response/body.rb +103 -18
- data/lib/http/response/inflater.rb +35 -7
- data/lib/http/response/parser.rb +98 -4
- data/lib/http/response/status/reasons.rb +2 -4
- data/lib/http/response/status.rb +141 -31
- data/lib/http/response.rb +219 -61
- data/lib/http/retriable/delay_calculator.rb +38 -11
- data/lib/http/retriable/errors.rb +21 -0
- data/lib/http/retriable/performer.rb +82 -38
- data/lib/http/session.rb +280 -0
- data/lib/http/timeout/global.rb +147 -34
- data/lib/http/timeout/null.rb +155 -9
- data/lib/http/timeout/per_operation.rb +139 -18
- data/lib/http/uri/normalizer.rb +82 -0
- data/lib/http/uri/parsing.rb +182 -0
- data/lib/http/uri.rb +289 -124
- data/lib/http/version.rb +2 -1
- data/lib/http.rb +11 -2
- data/sig/deps.rbs +122 -0
- data/sig/http.rbs +1619 -0
- data/test/http/base64_test.rb +28 -0
- data/test/http/client_test.rb +739 -0
- data/test/http/connection_test.rb +1533 -0
- data/test/http/content_type_test.rb +190 -0
- data/test/http/errors_test.rb +28 -0
- data/test/http/feature_test.rb +49 -0
- data/test/http/features/auto_deflate_test.rb +317 -0
- data/test/http/features/auto_inflate_test.rb +213 -0
- data/test/http/features/caching_test.rb +942 -0
- data/test/http/features/digest_auth_test.rb +996 -0
- data/test/http/features/instrumentation_test.rb +246 -0
- data/test/http/features/logging_test.rb +654 -0
- data/test/http/features/normalize_uri_test.rb +41 -0
- data/test/http/features/raise_error_test.rb +77 -0
- data/test/http/form_data/composite_io_test.rb +215 -0
- data/test/http/form_data/file_test.rb +255 -0
- data/test/http/form_data/fixtures/the-http-gem.info +1 -0
- data/test/http/form_data/multipart_test.rb +303 -0
- data/test/http/form_data/part_test.rb +90 -0
- data/test/http/form_data/urlencoded_test.rb +164 -0
- data/test/http/form_data_test.rb +232 -0
- data/test/http/headers/normalizer_test.rb +93 -0
- data/test/http/headers_test.rb +888 -0
- data/test/http/mime_type/json_test.rb +39 -0
- data/test/http/mime_type_test.rb +150 -0
- data/test/http/options/base_uri_test.rb +148 -0
- data/test/http/options/body_test.rb +21 -0
- data/test/http/options/features_test.rb +38 -0
- data/test/http/options/form_test.rb +21 -0
- data/test/http/options/headers_test.rb +32 -0
- data/test/http/options/json_test.rb +21 -0
- data/test/http/options/merge_test.rb +78 -0
- data/test/http/options/new_test.rb +37 -0
- data/test/http/options/proxy_test.rb +32 -0
- data/test/http/options_test.rb +575 -0
- data/test/http/redirector_test.rb +639 -0
- data/test/http/request/body_test.rb +318 -0
- data/test/http/request/builder_test.rb +623 -0
- data/test/http/request/writer_test.rb +391 -0
- data/test/http/request_test.rb +1733 -0
- data/test/http/response/body_test.rb +292 -0
- data/test/http/response/parser_test.rb +105 -0
- data/test/http/response/status_test.rb +322 -0
- data/test/http/response_test.rb +502 -0
- data/test/http/retriable/delay_calculator_test.rb +194 -0
- data/test/http/retriable/errors_test.rb +71 -0
- data/test/http/retriable/performer_test.rb +551 -0
- data/test/http/session_test.rb +424 -0
- data/test/http/timeout/global_test.rb +239 -0
- data/test/http/timeout/null_test.rb +218 -0
- data/test/http/timeout/per_operation_test.rb +220 -0
- data/test/http/uri/normalizer_test.rb +89 -0
- data/test/http/uri_test.rb +1140 -0
- data/test/http/version_test.rb +15 -0
- data/test/http_test.rb +818 -0
- data/test/regression_tests.rb +27 -0
- data/test/support/dummy_server/encoding_routes.rb +47 -0
- data/test/support/dummy_server/routes.rb +201 -0
- data/test/support/dummy_server/servlet.rb +81 -0
- data/test/support/dummy_server.rb +200 -0
- data/{spec → test}/support/fakeio.rb +2 -2
- data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
- data/test/support/http_handling_shared/timeout_tests.rb +134 -0
- data/test/support/http_handling_shared.rb +11 -0
- data/test/support/proxy_server.rb +207 -0
- data/test/support/servers/runner.rb +67 -0
- data/{spec → test}/support/simplecov.rb +11 -2
- data/test/support/ssl_helper.rb +108 -0
- data/test/test_helper.rb +38 -0
- metadata +108 -168
- data/.github/workflows/ci.yml +0 -67
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.rubocop/layout.yml +0 -8
- data/.rubocop/metrics.yml +0 -4
- data/.rubocop/rspec.yml +0 -9
- data/.rubocop/style.yml +0 -32
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -219
- data/.yardopts +0 -2
- data/CHANGES_OLD.md +0 -1002
- data/Gemfile +0 -51
- data/Guardfile +0 -18
- data/Rakefile +0 -64
- data/lib/http/headers/mixin.rb +0 -34
- data/lib/http/retriable/client.rb +0 -37
- data/logo.png +0 -0
- data/spec/lib/http/client_spec.rb +0 -556
- data/spec/lib/http/connection_spec.rb +0 -88
- data/spec/lib/http/content_type_spec.rb +0 -47
- data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
- data/spec/lib/http/features/instrumentation_spec.rb +0 -81
- data/spec/lib/http/features/logging_spec.rb +0 -65
- data/spec/lib/http/features/raise_error_spec.rb +0 -62
- data/spec/lib/http/headers/mixin_spec.rb +0 -36
- data/spec/lib/http/headers/normalizer_spec.rb +0 -52
- data/spec/lib/http/headers_spec.rb +0 -527
- data/spec/lib/http/options/body_spec.rb +0 -15
- data/spec/lib/http/options/features_spec.rb +0 -33
- data/spec/lib/http/options/form_spec.rb +0 -15
- data/spec/lib/http/options/headers_spec.rb +0 -24
- data/spec/lib/http/options/json_spec.rb +0 -15
- data/spec/lib/http/options/merge_spec.rb +0 -68
- data/spec/lib/http/options/new_spec.rb +0 -30
- data/spec/lib/http/options/proxy_spec.rb +0 -20
- data/spec/lib/http/options_spec.rb +0 -13
- data/spec/lib/http/redirector_spec.rb +0 -530
- data/spec/lib/http/request/body_spec.rb +0 -211
- data/spec/lib/http/request/writer_spec.rb +0 -121
- data/spec/lib/http/request_spec.rb +0 -234
- data/spec/lib/http/response/body_spec.rb +0 -85
- data/spec/lib/http/response/parser_spec.rb +0 -74
- data/spec/lib/http/response/status_spec.rb +0 -253
- data/spec/lib/http/response_spec.rb +0 -262
- data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
- data/spec/lib/http/retriable/performer_spec.rb +0 -302
- data/spec/lib/http/uri/normalizer_spec.rb +0 -95
- data/spec/lib/http/uri_spec.rb +0 -71
- data/spec/lib/http_spec.rb +0 -535
- data/spec/regression_specs.rb +0 -24
- data/spec/spec_helper.rb +0 -89
- data/spec/support/black_hole.rb +0 -13
- data/spec/support/dummy_server/servlet.rb +0 -203
- data/spec/support/dummy_server.rb +0 -44
- data/spec/support/fuubar.rb +0 -21
- data/spec/support/http_handling_shared.rb +0 -190
- data/spec/support/proxy_server.rb +0 -39
- data/spec/support/servers/config.rb +0 -11
- data/spec/support/servers/runner.rb +0 -19
- data/spec/support/ssl_helper.rb +0 -104
- /data/{spec → test}/support/capture_warning.rb +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPRetriableErrorsTest < Minitest::Test
|
|
6
|
+
cover "HTTP::OutOfRetriesError*"
|
|
7
|
+
|
|
8
|
+
def error
|
|
9
|
+
@error ||= HTTP::OutOfRetriesError.new("out of retries")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# -- #response --
|
|
13
|
+
|
|
14
|
+
def test_response_defaults_to_nil
|
|
15
|
+
assert_nil error.response
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_response_can_be_set_and_read
|
|
19
|
+
sentinel = Object.new
|
|
20
|
+
error.response = sentinel
|
|
21
|
+
|
|
22
|
+
assert_same sentinel, error.response
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# -- #cause --
|
|
26
|
+
|
|
27
|
+
def test_cause_returns_nil_when_no_cause_is_set
|
|
28
|
+
assert_nil error.cause
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_cause_returns_the_explicitly_set_cause
|
|
32
|
+
original = RuntimeError.new("boom")
|
|
33
|
+
error.cause = original
|
|
34
|
+
|
|
35
|
+
assert_same original, error.cause
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_cause_returns_the_implicit_cause_when_no_explicit_cause_is_set
|
|
39
|
+
implicit = RuntimeError.new("implicit")
|
|
40
|
+
|
|
41
|
+
err = begin
|
|
42
|
+
raise implicit
|
|
43
|
+
rescue RuntimeError
|
|
44
|
+
begin
|
|
45
|
+
raise HTTP::OutOfRetriesError, "out of retries"
|
|
46
|
+
rescue HTTP::OutOfRetriesError => e
|
|
47
|
+
e
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
assert_same implicit, err.cause
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_cause_prefers_the_explicit_cause_over_the_implicit_cause
|
|
55
|
+
explicit = RuntimeError.new("explicit")
|
|
56
|
+
implicit = RuntimeError.new("implicit")
|
|
57
|
+
|
|
58
|
+
err = begin
|
|
59
|
+
raise implicit
|
|
60
|
+
rescue RuntimeError
|
|
61
|
+
begin
|
|
62
|
+
raise HTTP::OutOfRetriesError, "out of retries"
|
|
63
|
+
rescue HTTP::OutOfRetriesError => e
|
|
64
|
+
e
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
err.cause = explicit
|
|
68
|
+
|
|
69
|
+
assert_same explicit, err.cause
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
# Custom exception used across performer tests
|
|
6
|
+
unless defined?(CustomException)
|
|
7
|
+
class CustomException < StandardError
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Subclass for testing is_a? vs instance_of? in retry_exception?
|
|
12
|
+
unless defined?(CustomSubException)
|
|
13
|
+
class CustomSubException < HTTP::TimeoutError
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class HTTPRetriablePerformerTest < Minitest::Test
|
|
18
|
+
cover "HTTP::Retriable::Performer*"
|
|
19
|
+
|
|
20
|
+
def client
|
|
21
|
+
@client ||= HTTP::Client.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def response
|
|
25
|
+
@response ||= HTTP::Response.new(
|
|
26
|
+
status: 200,
|
|
27
|
+
version: "1.1",
|
|
28
|
+
headers: {},
|
|
29
|
+
body: "Hello world!",
|
|
30
|
+
request: request
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def request
|
|
35
|
+
@request ||= HTTP::Request.new(
|
|
36
|
+
verb: :get,
|
|
37
|
+
uri: "http://example.com"
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def setup
|
|
42
|
+
super
|
|
43
|
+
@perform_spy = { counter: 0 }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def counter_spy
|
|
47
|
+
@perform_spy[:counter]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def perform(client_arg = client, request_arg = request, **options, &block)
|
|
51
|
+
# by explicitly overwriting the default delay, we make a much faster test suite
|
|
52
|
+
options = { delay: 0 }.merge(options)
|
|
53
|
+
|
|
54
|
+
HTTP::Retriable::Performer
|
|
55
|
+
.new(**options)
|
|
56
|
+
.perform(client_arg, request_arg) do
|
|
57
|
+
@perform_spy[:counter] += 1
|
|
58
|
+
block ? yield : response
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def measure_wait
|
|
63
|
+
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
64
|
+
result = yield
|
|
65
|
+
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
66
|
+
[t2 - t1, result]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# -- #initialize --
|
|
70
|
+
|
|
71
|
+
def test_initialize_coerces_tries_to_integer
|
|
72
|
+
performer = HTTP::Retriable::Performer.new(tries: 3.7)
|
|
73
|
+
|
|
74
|
+
assert_equal 3, performer.instance_variable_get(:@tries)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_initialize_coerces_string_tries_via_to_i
|
|
78
|
+
performer = HTTP::Retriable::Performer.new(tries: "3")
|
|
79
|
+
|
|
80
|
+
assert_equal 3, performer.instance_variable_get(:@tries)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def test_initialize_truncates_float_like_string_tries_via_to_i
|
|
84
|
+
performer = HTTP::Retriable::Performer.new(tries: "3.7")
|
|
85
|
+
|
|
86
|
+
assert_equal 3, performer.instance_variable_get(:@tries)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def test_initialize_uses_default_delay_when_none_provided
|
|
90
|
+
performer = HTTP::Retriable::Performer.new
|
|
91
|
+
delay = performer.calculate_delay(1, nil)
|
|
92
|
+
|
|
93
|
+
assert_operator delay, :>=, 0
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# -- #perform: expected exception --
|
|
97
|
+
|
|
98
|
+
def test_perform_expected_exception_retries_the_request
|
|
99
|
+
assert_raises HTTP::OutOfRetriesError do
|
|
100
|
+
perform(exceptions: [CustomException], tries: 2) do
|
|
101
|
+
raise CustomException
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
assert_equal 2, counter_spy
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def test_perform_expected_exception_retries_subclasses_of_listed_exceptions
|
|
108
|
+
assert_raises HTTP::OutOfRetriesError do
|
|
109
|
+
perform(exceptions: [HTTP::TimeoutError], tries: 2) do
|
|
110
|
+
raise CustomSubException
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
assert_equal 2, counter_spy
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# -- #perform: unexpected exception --
|
|
117
|
+
|
|
118
|
+
def test_perform_unexpected_exception_does_not_retry
|
|
119
|
+
assert_raises CustomException do
|
|
120
|
+
perform(exceptions: [], tries: 2) do
|
|
121
|
+
raise CustomException
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
assert_equal 1, counter_spy
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# -- #perform: expected status codes --
|
|
128
|
+
|
|
129
|
+
def make_response(**)
|
|
130
|
+
HTTP::Response.new(
|
|
131
|
+
status: 200,
|
|
132
|
+
version: "1.1",
|
|
133
|
+
headers: {},
|
|
134
|
+
body: "Hello world!",
|
|
135
|
+
request: request, **
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_perform_expected_status_retries_the_request
|
|
140
|
+
assert_raises HTTP::OutOfRetriesError do
|
|
141
|
+
perform(retry_statuses: [200], tries: 2)
|
|
142
|
+
end
|
|
143
|
+
assert_equal 2, counter_spy
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def test_perform_does_not_retry_when_range_does_not_cover_status
|
|
147
|
+
result = perform(retry_statuses: [400...500], tries: 2) do
|
|
148
|
+
make_response(status: 200)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
assert_equal 200, result.status.to_i
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def test_perform_does_not_retry_when_numeric_does_not_match_status
|
|
155
|
+
result = perform(retry_statuses: [500], tries: 2) do
|
|
156
|
+
make_response(status: 200)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
assert_equal 200, result.status.to_i
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_perform_does_not_retry_when_proc_returns_false
|
|
163
|
+
result = perform(retry_statuses: [->(s) { s >= 500 }], tries: 2) do
|
|
164
|
+
make_response(status: 200)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
assert_equal 200, result.status.to_i
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# -- status codes expressed in many ways --
|
|
171
|
+
|
|
172
|
+
[
|
|
173
|
+
301,
|
|
174
|
+
301.0,
|
|
175
|
+
[200, 301, 485],
|
|
176
|
+
250...400,
|
|
177
|
+
[250...Float::INFINITY],
|
|
178
|
+
->(status_code) { status_code == 301 },
|
|
179
|
+
[->(status_code) { status_code == 301 }]
|
|
180
|
+
].each do |retry_statuses|
|
|
181
|
+
define_method(:"test_perform_status_codes_#{retry_statuses}") do
|
|
182
|
+
assert_raises HTTP::OutOfRetriesError do
|
|
183
|
+
perform(retry_statuses: retry_statuses, tries: 2) do
|
|
184
|
+
make_response(status: 301)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# -- unexpected status code --
|
|
191
|
+
|
|
192
|
+
def test_perform_unexpected_status_does_not_retry
|
|
193
|
+
result = perform(retry_statuses: [], tries: 2)
|
|
194
|
+
|
|
195
|
+
assert_equal response, result
|
|
196
|
+
assert_equal 1, counter_spy
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# -- on_retry callback --
|
|
200
|
+
|
|
201
|
+
def test_on_retry_callback_with_exception
|
|
202
|
+
callback_call_spy = 0
|
|
203
|
+
|
|
204
|
+
callback_spy = proc do |callback_request, error, callback_response|
|
|
205
|
+
assert_equal request, callback_request
|
|
206
|
+
assert_kind_of HTTP::TimeoutError, error
|
|
207
|
+
assert_nil callback_response
|
|
208
|
+
callback_call_spy += 1
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
assert_raises HTTP::OutOfRetriesError do
|
|
212
|
+
perform(tries: 3, on_retry: callback_spy) do
|
|
213
|
+
raise HTTP::TimeoutError
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
assert_equal 2, callback_call_spy
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def test_on_retry_callback_with_response
|
|
221
|
+
callback_call_spy = 0
|
|
222
|
+
|
|
223
|
+
callback_spy = proc do |callback_request, error, callback_response|
|
|
224
|
+
assert_equal request, callback_request
|
|
225
|
+
assert_nil error
|
|
226
|
+
assert_equal response, callback_response
|
|
227
|
+
callback_call_spy += 1
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
assert_raises HTTP::OutOfRetriesError do
|
|
231
|
+
perform(retry_statuses: [200], tries: 3, on_retry: callback_spy)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
assert_equal 2, callback_call_spy
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# -- delay option --
|
|
238
|
+
|
|
239
|
+
def test_delay_sleeps_for_the_calculated_delay
|
|
240
|
+
slept_values = []
|
|
241
|
+
performer = HTTP::Retriable::Performer.new(delay: 0.123, tries: 2, should_retry: ->(*) { true })
|
|
242
|
+
performer.define_singleton_method(:sleep) { |d| slept_values << d }
|
|
243
|
+
|
|
244
|
+
assert_raises(HTTP::OutOfRetriesError) do
|
|
245
|
+
performer.perform(client, request) { response }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
assert_equal [0.123], slept_values
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def test_delay_can_be_a_positive_number
|
|
252
|
+
timing_slack = 0.5
|
|
253
|
+
time, = measure_wait do
|
|
254
|
+
assert_raises(HTTP::OutOfRetriesError) do
|
|
255
|
+
perform(delay: 0.02, tries: 3, should_retry: ->(*) { true })
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
assert_in_delta 0.04, time, timing_slack
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def test_delay_can_be_a_proc_number
|
|
263
|
+
timing_slack = 0.5
|
|
264
|
+
time, = measure_wait do
|
|
265
|
+
assert_raises(HTTP::OutOfRetriesError) do
|
|
266
|
+
perform(delay: ->(attempt) { attempt / 50.0 }, tries: 3, should_retry: ->(*) { true })
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
assert_in_delta 0.06, time, timing_slack
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def test_delay_receives_correct_retry_number_when_a_proc
|
|
274
|
+
retry_count = 0
|
|
275
|
+
retry_proc = proc do |attempt|
|
|
276
|
+
assert_equal retry_count, attempt
|
|
277
|
+
assert_operator attempt, :>, 0
|
|
278
|
+
0
|
|
279
|
+
end
|
|
280
|
+
assert_raises(HTTP::OutOfRetriesError) do
|
|
281
|
+
perform(delay: retry_proc, should_retry: ->(*) { true }) do
|
|
282
|
+
retry_count += 1
|
|
283
|
+
response
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def test_delay_respects_max_delay_option
|
|
289
|
+
timing_slack = 0.5
|
|
290
|
+
time, = measure_wait do
|
|
291
|
+
assert_raises(HTTP::OutOfRetriesError) do
|
|
292
|
+
perform(delay: 100, max_delay: 0.02, tries: 3, should_retry: ->(*) { true })
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
assert_in_delta 0.04, time, timing_slack
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# -- should_retry option --
|
|
300
|
+
|
|
301
|
+
def test_should_retry_decides_if_request_should_be_retried
|
|
302
|
+
retry_proc = proc do |req, err, res, attempt|
|
|
303
|
+
assert_equal request, req
|
|
304
|
+
if res
|
|
305
|
+
assert_nil err
|
|
306
|
+
assert_equal response, res
|
|
307
|
+
else
|
|
308
|
+
assert_kind_of CustomException, err
|
|
309
|
+
assert_nil res
|
|
310
|
+
end
|
|
311
|
+
attempt < 5
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
begin
|
|
315
|
+
perform(should_retry: retry_proc) do
|
|
316
|
+
rand < 0.5 ? response : raise(CustomException)
|
|
317
|
+
end
|
|
318
|
+
rescue CustomException
|
|
319
|
+
nil
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
assert_equal 5, counter_spy
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def test_should_retry_passes_the_exception_to_proc
|
|
326
|
+
received_err = nil
|
|
327
|
+
retry_proc = proc do |_req, err, _res, _attempt|
|
|
328
|
+
received_err = err
|
|
329
|
+
false
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
assert_raises CustomException do
|
|
333
|
+
perform(should_retry: retry_proc) do
|
|
334
|
+
raise CustomException
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
assert_kind_of CustomException, received_err
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def test_should_retry_raises_original_error_if_not_retryable
|
|
342
|
+
retry_proc = ->(*) { false }
|
|
343
|
+
|
|
344
|
+
assert_raises CustomException do
|
|
345
|
+
perform(should_retry: retry_proc) do
|
|
346
|
+
raise CustomException
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
assert_equal 1, counter_spy
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def test_should_retry_raises_out_of_retries_error_if_retryable
|
|
354
|
+
retry_proc = ->(*) { true }
|
|
355
|
+
|
|
356
|
+
assert_raises HTTP::OutOfRetriesError do
|
|
357
|
+
perform(should_retry: retry_proc) do
|
|
358
|
+
raise CustomException
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
assert_equal 5, counter_spy
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# -- #calculate_delay --
|
|
366
|
+
|
|
367
|
+
def test_calculate_delay_passes_response_to_delay_calculator
|
|
368
|
+
responses_seen = []
|
|
369
|
+
|
|
370
|
+
performer = HTTP::Retriable::Performer.new(delay: 0, retry_statuses: [200], tries: 2)
|
|
371
|
+
calculator = performer.instance_variable_get(:@delay_calculator)
|
|
372
|
+
original_call = calculator.method(:call)
|
|
373
|
+
calculator.define_singleton_method(:call) do |iteration, resp|
|
|
374
|
+
responses_seen << resp
|
|
375
|
+
original_call.call(iteration, resp)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
begin
|
|
379
|
+
performer.perform(client, request) { response }
|
|
380
|
+
rescue HTTP::OutOfRetriesError
|
|
381
|
+
nil
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
assert_equal response, responses_seen.first
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# -- when block returns nil --
|
|
388
|
+
|
|
389
|
+
def test_when_block_returns_nil_continues_iterating
|
|
390
|
+
call_count = 0
|
|
391
|
+
perform(tries: 3) do
|
|
392
|
+
call_count += 1
|
|
393
|
+
call_count < 2 ? nil : response
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
assert_equal 2, call_count
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# -- connection closing --
|
|
400
|
+
|
|
401
|
+
def test_connection_closing_does_not_close_on_proper_response
|
|
402
|
+
close_called = false
|
|
403
|
+
mock_client = fake(close: ->(*) { close_called = true })
|
|
404
|
+
perform(mock_client)
|
|
405
|
+
|
|
406
|
+
refute close_called
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def test_connection_closing_closes_after_each_raised_attempt
|
|
410
|
+
close_count = 0
|
|
411
|
+
mock_client = fake(close: ->(*) { close_count += 1 })
|
|
412
|
+
|
|
413
|
+
assert_raises(HTTP::OutOfRetriesError) do
|
|
414
|
+
perform(mock_client, should_retry: ->(*) { true }, tries: 3)
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
assert_equal 3, close_count
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def test_connection_closing_closes_on_unexpected_exception
|
|
421
|
+
close_count = 0
|
|
422
|
+
mock_client = fake(close: ->(*) { close_count += 1 })
|
|
423
|
+
|
|
424
|
+
assert_raises(CustomException) do
|
|
425
|
+
perform(mock_client) do
|
|
426
|
+
raise CustomException
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
assert_equal 1, close_count
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# -- response flushing on exhausted retries --
|
|
434
|
+
|
|
435
|
+
def test_response_flushing_flushes_when_retries_exhausted
|
|
436
|
+
flushed = false
|
|
437
|
+
flush_response = HTTP::Response.new(
|
|
438
|
+
status: 503,
|
|
439
|
+
version: "1.1",
|
|
440
|
+
headers: {},
|
|
441
|
+
body: "Service Unavailable",
|
|
442
|
+
request: request
|
|
443
|
+
)
|
|
444
|
+
flush_response.define_singleton_method(:flush) do
|
|
445
|
+
flushed = true
|
|
446
|
+
self
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
begin
|
|
450
|
+
HTTP::Retriable::Performer
|
|
451
|
+
.new(delay: 0, retry_statuses: [503], tries: 2)
|
|
452
|
+
.perform(client, request) { flush_response }
|
|
453
|
+
rescue HTTP::OutOfRetriesError
|
|
454
|
+
nil
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
assert flushed, "expected response to be flushed on final attempt"
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# -- HTTP::OutOfRetriesError --
|
|
461
|
+
|
|
462
|
+
def test_out_of_retries_error_has_original_exception_as_cause
|
|
463
|
+
err = nil
|
|
464
|
+
begin
|
|
465
|
+
perform(exceptions: [CustomException]) do
|
|
466
|
+
raise CustomException
|
|
467
|
+
end
|
|
468
|
+
rescue HTTP::OutOfRetriesError => e
|
|
469
|
+
err = e
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
assert_kind_of CustomException, err.cause
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def test_out_of_retries_error_has_last_response_as_attribute
|
|
476
|
+
err = nil
|
|
477
|
+
begin
|
|
478
|
+
perform(should_retry: ->(*) { true })
|
|
479
|
+
rescue HTTP::OutOfRetriesError => e
|
|
480
|
+
err = e
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
assert_equal response, err.response
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def test_out_of_retries_error_has_message_containing_verb_and_uri
|
|
487
|
+
err = nil
|
|
488
|
+
begin
|
|
489
|
+
perform(exceptions: [CustomException]) do
|
|
490
|
+
raise CustomException
|
|
491
|
+
end
|
|
492
|
+
rescue HTTP::OutOfRetriesError => e
|
|
493
|
+
err = e
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
assert_includes err.message, "GET"
|
|
497
|
+
assert_includes err.message, "http://example.com"
|
|
498
|
+
assert_includes err.message, "failed"
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def test_out_of_retries_error_includes_status_when_response_present
|
|
502
|
+
err = nil
|
|
503
|
+
begin
|
|
504
|
+
perform(retry_statuses: [200], tries: 2)
|
|
505
|
+
rescue HTTP::OutOfRetriesError => e
|
|
506
|
+
err = e
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
assert_includes err.message, "200"
|
|
510
|
+
assert_includes err.message, "GET"
|
|
511
|
+
assert_includes err.message, "http://example.com"
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def test_out_of_retries_error_includes_exception_in_message
|
|
515
|
+
err = nil
|
|
516
|
+
begin
|
|
517
|
+
perform(exceptions: [CustomException]) do
|
|
518
|
+
raise CustomException, "something went wrong"
|
|
519
|
+
end
|
|
520
|
+
rescue HTTP::OutOfRetriesError => e
|
|
521
|
+
err = e
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
assert_includes err.message, "something went wrong"
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def test_out_of_retries_error_does_not_include_status_when_no_response
|
|
528
|
+
err = nil
|
|
529
|
+
begin
|
|
530
|
+
perform(exceptions: [CustomException]) do
|
|
531
|
+
raise CustomException
|
|
532
|
+
end
|
|
533
|
+
rescue HTTP::OutOfRetriesError => e
|
|
534
|
+
err = e
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
refute_includes err.message, " with "
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def test_out_of_retries_error_does_not_include_exception_when_no_exception
|
|
541
|
+
err = nil
|
|
542
|
+
begin
|
|
543
|
+
perform(retry_statuses: [200], tries: 2)
|
|
544
|
+
rescue HTTP::OutOfRetriesError => e
|
|
545
|
+
err = e
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
assert_match(/failed with [\w ]+\z/, err.message)
|
|
549
|
+
assert_includes err.message, " with "
|
|
550
|
+
end
|
|
551
|
+
end
|