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,623 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPRequestBuilderTest < Minitest::Test
|
|
6
|
+
cover "HTTP::Request::Builder*"
|
|
7
|
+
|
|
8
|
+
def build_builder(**option_overrides)
|
|
9
|
+
HTTP::Request::Builder.new(HTTP::Options.new(**option_overrides))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# #build basics
|
|
13
|
+
|
|
14
|
+
def test_build_returns_an_http_request
|
|
15
|
+
builder = build_builder
|
|
16
|
+
request = builder.build(:get, "http://example.com/path")
|
|
17
|
+
|
|
18
|
+
assert_kind_of HTTP::Request, request
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_build_sets_the_verb_on_the_request
|
|
22
|
+
builder = build_builder
|
|
23
|
+
request = builder.build(:get, "http://example.com/path")
|
|
24
|
+
|
|
25
|
+
assert_equal :get, request.verb
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_build_sets_the_uri_on_the_request
|
|
29
|
+
builder = build_builder
|
|
30
|
+
request = builder.build(:get, "http://example.com/path")
|
|
31
|
+
|
|
32
|
+
assert_equal "/path", request.uri.path
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_build_sets_connection_close_by_default
|
|
36
|
+
builder = build_builder
|
|
37
|
+
request = builder.build(:get, "http://example.com/path")
|
|
38
|
+
|
|
39
|
+
assert_equal HTTP::Connection::CLOSE, request.headers["Connection"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_build_sets_the_proxy_from_options
|
|
43
|
+
opts = HTTP::Options.new(proxy: { proxy_address: "proxy.example.com" })
|
|
44
|
+
b = HTTP::Request::Builder.new(opts)
|
|
45
|
+
req = b.build(:get, "http://example.com/")
|
|
46
|
+
|
|
47
|
+
assert_equal({ proxy_address: "proxy.example.com" }, req.proxy)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_build_with_persistent_connection_sets_keep_alive
|
|
51
|
+
builder = build_builder(persistent: "http://example.com")
|
|
52
|
+
request = builder.build(:get, "http://example.com/path")
|
|
53
|
+
|
|
54
|
+
assert_equal HTTP::Connection::KEEP_ALIVE, request.headers["Connection"]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_build_when_uri_has_empty_path_sets_path_to_slash
|
|
58
|
+
builder = build_builder
|
|
59
|
+
request = builder.build(:get, "http://example.com")
|
|
60
|
+
|
|
61
|
+
assert_equal "/", request.uri.path
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_build_when_uri_has_a_non_empty_path_preserves_it
|
|
65
|
+
builder = build_builder
|
|
66
|
+
request = builder.build(:get, "http://example.com/foo")
|
|
67
|
+
|
|
68
|
+
assert_equal "/foo", request.uri.path
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_build_with_query_params_in_options_merges_into_uri_query
|
|
72
|
+
builder = build_builder(params: { "foo" => "bar" })
|
|
73
|
+
request = builder.build(:get, "http://example.com/path")
|
|
74
|
+
|
|
75
|
+
assert_includes request.uri.query, "foo=bar"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_build_with_query_params_and_existing_query_preserves_existing
|
|
79
|
+
builder = build_builder(params: { "extra" => "val" })
|
|
80
|
+
request = builder.build(:get, "http://example.com/path?existing=1")
|
|
81
|
+
|
|
82
|
+
assert_includes request.uri.query, "existing=1"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_build_with_query_params_and_existing_query_appends_new_params
|
|
86
|
+
builder = build_builder(params: { "extra" => "val" })
|
|
87
|
+
request = builder.build(:get, "http://example.com/path?existing=1")
|
|
88
|
+
|
|
89
|
+
assert_includes request.uri.query, "extra=val"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_build_with_body_in_options_uses_body
|
|
93
|
+
builder = build_builder(body: "raw body")
|
|
94
|
+
req = builder.build(:post, "http://example.com/")
|
|
95
|
+
chunks = req.body.enum_for(:each).map(&:dup)
|
|
96
|
+
|
|
97
|
+
assert_equal ["raw body"], chunks
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_build_with_form_data_in_options_sets_content_type_header
|
|
101
|
+
builder = build_builder(form: { "key" => "value" })
|
|
102
|
+
req = builder.build(:post, "http://example.com/")
|
|
103
|
+
|
|
104
|
+
refute_nil req.headers["Content-Type"]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def test_build_with_form_data_in_options_includes_form_data_in_body
|
|
108
|
+
builder = build_builder(form: { "key" => "value" })
|
|
109
|
+
req = builder.build(:post, "http://example.com/")
|
|
110
|
+
chunks = req.body.enum_for(:each).map(&:dup)
|
|
111
|
+
body_str = chunks.join
|
|
112
|
+
|
|
113
|
+
assert_includes body_str, "key=value"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_build_with_json_in_options_encodes_json_body
|
|
117
|
+
builder = build_builder(json: { "key" => "value" })
|
|
118
|
+
req = builder.build(:post, "http://example.com/")
|
|
119
|
+
chunks = req.body.enum_for(:each).map(&:dup)
|
|
120
|
+
|
|
121
|
+
assert_equal [{ "key" => "value" }.to_json], chunks
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_build_with_json_in_options_sets_content_type_to_application_json
|
|
125
|
+
builder = build_builder(json: { "key" => "value" })
|
|
126
|
+
req = builder.build(:post, "http://example.com/")
|
|
127
|
+
|
|
128
|
+
assert_match(%r{\Aapplication/json}, req.headers["Content-Type"])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_build_with_normalize_uri_feature_passes_custom_normalizer
|
|
132
|
+
custom_normalizer = ->(uri) { HTTP::URI::NORMALIZER.call(uri) }
|
|
133
|
+
builder = build_builder(features: { normalize_uri: { normalizer: custom_normalizer } })
|
|
134
|
+
req = builder.build(:get, "http://example.com/path")
|
|
135
|
+
|
|
136
|
+
assert_same custom_normalizer, req.uri_normalizer
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_build_without_normalize_uri_feature_uses_default_normalizer
|
|
140
|
+
builder = build_builder
|
|
141
|
+
req = builder.build(:get, "http://example.com/path")
|
|
142
|
+
|
|
143
|
+
assert_equal HTTP::URI::NORMALIZER, req.uri_normalizer
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def test_build_with_an_object_responding_to_to_s_as_uri_converts_it
|
|
147
|
+
builder = build_builder
|
|
148
|
+
uri_obj = Object.new
|
|
149
|
+
uri_obj.define_singleton_method(:to_s) { "http://example.com/converted" }
|
|
150
|
+
req = builder.build(:get, uri_obj)
|
|
151
|
+
|
|
152
|
+
assert_equal "/converted", req.uri.path
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_build_with_uri_object_and_base_uri_converts_non_string_uri
|
|
156
|
+
builder = build_builder(base_uri: "http://example.com/api/")
|
|
157
|
+
uri_obj = Object.new
|
|
158
|
+
uri_obj.define_singleton_method(:to_s) { "users" }
|
|
159
|
+
req = builder.build(:get, uri_obj)
|
|
160
|
+
|
|
161
|
+
assert_equal "example.com", req.uri.host
|
|
162
|
+
assert_equal "/api/users", req.uri.path
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def test_build_with_a_feature_that_wraps_the_request_returns_wrapped
|
|
166
|
+
wrapped = nil
|
|
167
|
+
feature_class = Class.new(HTTP::Feature) do
|
|
168
|
+
define_method(:wrap_request) do |req|
|
|
169
|
+
wrapped = HTTP::Request.new(
|
|
170
|
+
verb: req.verb,
|
|
171
|
+
uri: "http://wrapped.example.com/wrapped"
|
|
172
|
+
)
|
|
173
|
+
wrapped
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
HTTP::Options.register_feature(:test_build_wrap, feature_class)
|
|
178
|
+
begin
|
|
179
|
+
opts = HTTP::Options.new(features: { test_build_wrap: {} })
|
|
180
|
+
b = HTTP::Request::Builder.new(opts)
|
|
181
|
+
result = b.build(:get, "http://example.com/original")
|
|
182
|
+
|
|
183
|
+
assert_same wrapped, result
|
|
184
|
+
assert_equal "/wrapped", result.uri.path
|
|
185
|
+
ensure
|
|
186
|
+
HTTP::Options.available_features.delete(:test_build_wrap)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# #build with base_uri
|
|
191
|
+
|
|
192
|
+
def test_build_with_base_uri_when_uri_is_relative_resolves_against_base
|
|
193
|
+
builder = build_builder(base_uri: "http://example.com/api/")
|
|
194
|
+
req = builder.build(:get, "users")
|
|
195
|
+
|
|
196
|
+
assert_equal "example.com", req.uri.host
|
|
197
|
+
assert_match(%r{/api/users}, req.uri.path)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def test_build_with_base_uri_path_not_ending_with_slash_appends_slash
|
|
201
|
+
builder = build_builder(base_uri: "http://example.com/api")
|
|
202
|
+
req = builder.build(:get, "users")
|
|
203
|
+
|
|
204
|
+
assert_equal "example.com", req.uri.host
|
|
205
|
+
assert_match(%r{/api/users}, req.uri.path)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def test_build_with_base_uri_path_not_ending_with_slash_does_not_mutate_original
|
|
209
|
+
options = HTTP::Options.new(base_uri: "http://example.com/api")
|
|
210
|
+
builder = HTTP::Request::Builder.new(options)
|
|
211
|
+
original_path = options.base_uri.path.dup
|
|
212
|
+
builder.build(:get, "users")
|
|
213
|
+
|
|
214
|
+
assert_equal original_path, options.base_uri.path
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def test_build_with_base_uri_path_ending_with_slash_does_not_double_it
|
|
218
|
+
builder = build_builder(base_uri: "http://example.com/api/")
|
|
219
|
+
req = builder.build(:get, "users")
|
|
220
|
+
|
|
221
|
+
assert_equal "/api/users", req.uri.path
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def test_build_with_base_uri_and_absolute_http_uri_does_not_use_base
|
|
225
|
+
builder = build_builder(base_uri: "http://example.com/api/")
|
|
226
|
+
req = builder.build(:get, "http://other.com/path")
|
|
227
|
+
|
|
228
|
+
assert_equal "other.com", req.uri.host
|
|
229
|
+
assert_equal "/path", req.uri.path
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def test_build_with_base_uri_and_absolute_https_uri_does_not_use_base
|
|
233
|
+
builder = build_builder(base_uri: "http://example.com/api/")
|
|
234
|
+
req = builder.build(:get, "https://secure.example.com/path")
|
|
235
|
+
|
|
236
|
+
assert_equal "secure.example.com", req.uri.host
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def test_build_with_base_uri_resolves_correctly_when_base_is_set
|
|
240
|
+
opts = HTTP::Options.new(base_uri: "http://example.com/")
|
|
241
|
+
b = HTTP::Request::Builder.new(opts)
|
|
242
|
+
req = b.build(:get, "relative")
|
|
243
|
+
|
|
244
|
+
assert_equal "example.com", req.uri.host
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# #build with persistent
|
|
248
|
+
|
|
249
|
+
def test_build_with_persistent_when_uri_is_relative_prepends_persistent_origin
|
|
250
|
+
builder = build_builder(persistent: "http://example.com")
|
|
251
|
+
req = builder.build(:get, "/path")
|
|
252
|
+
|
|
253
|
+
assert_equal "example.com", req.uri.host
|
|
254
|
+
assert_equal "/path", req.uri.path
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def test_build_with_persistent_does_not_prepend_when_not_persistent
|
|
258
|
+
non_persistent_opts = HTTP::Options.new
|
|
259
|
+
b = HTTP::Request::Builder.new(non_persistent_opts)
|
|
260
|
+
req = b.build(:get, "http://fallback.com/path")
|
|
261
|
+
|
|
262
|
+
assert_equal "fallback.com", req.uri.host
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def test_build_with_persistent_when_uri_is_absolute_uses_absolute_uri
|
|
266
|
+
builder = build_builder(persistent: "http://example.com")
|
|
267
|
+
req = builder.build(:get, "http://other.com/path")
|
|
268
|
+
|
|
269
|
+
assert_equal "other.com", req.uri.host
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# #wrap
|
|
273
|
+
|
|
274
|
+
def test_wrap_returns_the_request_when_no_features_configured
|
|
275
|
+
builder = build_builder
|
|
276
|
+
req = HTTP::Request.new(verb: :get, uri: "http://example.com/")
|
|
277
|
+
result = builder.wrap(req)
|
|
278
|
+
|
|
279
|
+
assert_same req, result
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def test_wrap_with_a_feature_applies_feature_wrapping
|
|
283
|
+
wrapped_request = nil
|
|
284
|
+
feature_class = Class.new(HTTP::Feature) do
|
|
285
|
+
define_method(:wrap_request) do |req|
|
|
286
|
+
wrapped_request = req
|
|
287
|
+
req
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
HTTP::Options.register_feature(:test_wrap_builder, feature_class)
|
|
292
|
+
begin
|
|
293
|
+
opts = HTTP::Options.new(features: { test_wrap_builder: {} })
|
|
294
|
+
b = HTTP::Request::Builder.new(opts)
|
|
295
|
+
req = HTTP::Request.new(verb: :get, uri: "http://example.com/")
|
|
296
|
+
b.wrap(req)
|
|
297
|
+
|
|
298
|
+
assert_same req, wrapped_request
|
|
299
|
+
ensure
|
|
300
|
+
HTTP::Options.available_features.delete(:test_wrap_builder)
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def test_wrap_with_multiple_features_applies_in_order
|
|
305
|
+
call_order = []
|
|
306
|
+
feature_a = Class.new(HTTP::Feature) do
|
|
307
|
+
define_method(:wrap_request) do |req|
|
|
308
|
+
call_order << :a
|
|
309
|
+
req
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
feature_b = Class.new(HTTP::Feature) do
|
|
313
|
+
define_method(:wrap_request) do |req|
|
|
314
|
+
call_order << :b
|
|
315
|
+
req
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
HTTP::Options.register_feature(:test_wrap_a, feature_a)
|
|
320
|
+
HTTP::Options.register_feature(:test_wrap_b, feature_b)
|
|
321
|
+
begin
|
|
322
|
+
opts = HTTP::Options.new(features: { test_wrap_a: {}, test_wrap_b: {} })
|
|
323
|
+
b = HTTP::Request::Builder.new(opts)
|
|
324
|
+
req = HTTP::Request.new(verb: :get, uri: "http://example.com/")
|
|
325
|
+
b.wrap(req)
|
|
326
|
+
|
|
327
|
+
assert_equal %i[a b], call_order
|
|
328
|
+
ensure
|
|
329
|
+
HTTP::Options.available_features.delete(:test_wrap_a)
|
|
330
|
+
HTTP::Options.available_features.delete(:test_wrap_b)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# make_request_body (via #build)
|
|
335
|
+
|
|
336
|
+
def test_make_request_body_when_body_option_is_set_uses_body_directly
|
|
337
|
+
builder = build_builder(body: "raw")
|
|
338
|
+
req = builder.build(:post, "http://example.com/")
|
|
339
|
+
chunks = req.body.enum_for(:each).map(&:dup)
|
|
340
|
+
|
|
341
|
+
assert_equal ["raw"], chunks
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def test_make_request_body_when_form_option_is_set_creates_form_data
|
|
345
|
+
builder = build_builder(form: { "name" => "test" })
|
|
346
|
+
req = builder.build(:post, "http://example.com/")
|
|
347
|
+
|
|
348
|
+
refute_nil req.headers["Content-Type"]
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def test_make_request_body_when_form_option_is_set_returns_form_data_body_source
|
|
352
|
+
builder = build_builder(form: { "name" => "test" })
|
|
353
|
+
req = builder.build(:post, "http://example.com/")
|
|
354
|
+
|
|
355
|
+
refute_nil req.body.source
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def test_make_request_body_form_does_not_override_existing_content_type
|
|
359
|
+
opts = HTTP::Options.new(
|
|
360
|
+
form: { "name" => "test" },
|
|
361
|
+
headers: { "Content-Type" => "custom/type" }
|
|
362
|
+
)
|
|
363
|
+
b = HTTP::Request::Builder.new(opts)
|
|
364
|
+
req = b.build(:post, "http://example.com/")
|
|
365
|
+
|
|
366
|
+
assert_equal "custom/type", req.headers["Content-Type"]
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def test_make_request_body_when_json_option_is_set_encodes_as_json
|
|
370
|
+
builder = build_builder(json: { "key" => "val" })
|
|
371
|
+
req = builder.build(:post, "http://example.com/")
|
|
372
|
+
chunks = req.body.enum_for(:each).map(&:dup)
|
|
373
|
+
|
|
374
|
+
assert_equal [{ "key" => "val" }.to_json], chunks
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def test_make_request_body_json_includes_charset_in_content_type
|
|
378
|
+
builder = build_builder(json: { "key" => "val" })
|
|
379
|
+
req = builder.build(:post, "http://example.com/")
|
|
380
|
+
|
|
381
|
+
assert_match(/charset=utf-8/, req.headers["Content-Type"])
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def test_make_request_body_json_does_not_override_existing_content_type
|
|
385
|
+
opts = HTTP::Options.new(
|
|
386
|
+
json: { "key" => "val" },
|
|
387
|
+
headers: { "Content-Type" => "custom/json" }
|
|
388
|
+
)
|
|
389
|
+
b = HTTP::Request::Builder.new(opts)
|
|
390
|
+
req = b.build(:post, "http://example.com/")
|
|
391
|
+
|
|
392
|
+
assert_equal "custom/json", req.headers["Content-Type"]
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def test_make_request_body_when_no_body_form_or_json_has_nil_body_source
|
|
396
|
+
builder = build_builder
|
|
397
|
+
req = builder.build(:get, "http://example.com/")
|
|
398
|
+
|
|
399
|
+
assert_nil req.body.source
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# make_form_data (via #build)
|
|
403
|
+
|
|
404
|
+
def test_make_form_data_with_hash_form_creates_form_data
|
|
405
|
+
builder = build_builder(form: { "field" => "value" })
|
|
406
|
+
req = builder.build(:post, "http://example.com/")
|
|
407
|
+
|
|
408
|
+
refute_nil req.headers["Content-Type"]
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def test_make_form_data_with_hash_form_passes_data_through_to_body
|
|
412
|
+
builder = build_builder(form: { "field" => "value" })
|
|
413
|
+
req = builder.build(:post, "http://example.com/")
|
|
414
|
+
chunks = req.body.enum_for(:each).map(&:dup)
|
|
415
|
+
body_str = chunks.join
|
|
416
|
+
|
|
417
|
+
assert_includes body_str, "field=value"
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def test_make_form_data_with_multipart_form_passes_through
|
|
421
|
+
multipart = HTTP::FormData::Multipart.new({ "part" => HTTP::FormData::Part.new("val") })
|
|
422
|
+
opts = HTTP::Options.new(form: multipart)
|
|
423
|
+
b = HTTP::Request::Builder.new(opts)
|
|
424
|
+
req = b.build(:post, "http://example.com/")
|
|
425
|
+
|
|
426
|
+
assert_match(%r{\Amultipart/form-data}, req.headers["Content-Type"])
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def test_make_form_data_with_urlencoded_form_passes_through
|
|
430
|
+
urlencoded = HTTP::FormData::Urlencoded.new({ "field" => "value" })
|
|
431
|
+
opts = HTTP::Options.new(form: urlencoded)
|
|
432
|
+
b = HTTP::Request::Builder.new(opts)
|
|
433
|
+
req = b.build(:post, "http://example.com/")
|
|
434
|
+
|
|
435
|
+
assert_equal "application/x-www-form-urlencoded", req.headers["Content-Type"]
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# merge_query_params!
|
|
439
|
+
|
|
440
|
+
def test_merge_query_params_when_params_is_nil_does_not_add_query_string
|
|
441
|
+
builder = build_builder
|
|
442
|
+
req = builder.build(:get, "http://example.com/path")
|
|
443
|
+
|
|
444
|
+
assert_nil req.uri.query
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def test_merge_query_params_when_params_is_empty_does_not_add_query_string
|
|
448
|
+
builder = build_builder(params: {})
|
|
449
|
+
req = builder.build(:get, "http://example.com/path")
|
|
450
|
+
|
|
451
|
+
assert_nil req.uri.query
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def test_merge_query_params_when_params_has_values_and_no_query_sets_query
|
|
455
|
+
builder = build_builder(params: { "a" => "1" })
|
|
456
|
+
req = builder.build(:get, "http://example.com/path")
|
|
457
|
+
|
|
458
|
+
assert_equal "a=1", req.uri.query
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def test_merge_query_params_when_params_has_values_and_existing_query_concatenates
|
|
462
|
+
builder = build_builder(params: { "b" => "2" })
|
|
463
|
+
req = builder.build(:get, "http://example.com/path?a=1")
|
|
464
|
+
|
|
465
|
+
assert_equal "a=1&b=2", req.uri.query
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# empty path normalization (via #build)
|
|
469
|
+
|
|
470
|
+
def test_empty_path_normalization_normalizes_to_slash
|
|
471
|
+
builder = build_builder
|
|
472
|
+
req = builder.build(:get, "http://example.com")
|
|
473
|
+
|
|
474
|
+
assert_equal "/", req.uri.path
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def test_empty_path_normalization_returns_http_uri_with_corrected_path
|
|
478
|
+
builder = build_builder
|
|
479
|
+
req = builder.build(:get, "http://example.com")
|
|
480
|
+
|
|
481
|
+
assert_instance_of HTTP::URI, req.uri
|
|
482
|
+
assert_equal "/", req.uri.path
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# resolve_against_base error handling (via #build)
|
|
486
|
+
|
|
487
|
+
def test_resolve_against_base_raises_http_error
|
|
488
|
+
opts = HTTP::Options.new(base_uri: "http://example.com/")
|
|
489
|
+
b = HTTP::Request::Builder.new(opts)
|
|
490
|
+
opts.define_singleton_method(:base_uri) { nil }
|
|
491
|
+
opts.define_singleton_method(:base_uri?) { true }
|
|
492
|
+
|
|
493
|
+
err = assert_raises(HTTP::Error) { b.build(:get, "relative") }
|
|
494
|
+
assert_equal "base_uri is not set", err.message
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# make_request_uri scheme guard with base_uri
|
|
498
|
+
|
|
499
|
+
def test_scheme_guard_with_base_uri_and_absolute_http_uses_absolute_uri
|
|
500
|
+
builder = build_builder(base_uri: "http://example.com/api/")
|
|
501
|
+
req = builder.build(:get, "http://other.com/path")
|
|
502
|
+
|
|
503
|
+
assert_equal "other.com", req.uri.host
|
|
504
|
+
assert_equal "/path", req.uri.path
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def test_scheme_guard_with_base_uri_and_absolute_https_uses_absolute_uri
|
|
508
|
+
builder = build_builder(base_uri: "http://example.com/api/")
|
|
509
|
+
req = builder.build(:get, "https://secure.com/path")
|
|
510
|
+
|
|
511
|
+
assert_equal "secure.com", req.uri.host
|
|
512
|
+
assert_equal "/path", req.uri.path
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def test_scheme_guard_with_base_uri_and_relative_uri_resolves_against_base
|
|
516
|
+
builder = build_builder(base_uri: "http://example.com/api/")
|
|
517
|
+
req = builder.build(:get, "users/1")
|
|
518
|
+
|
|
519
|
+
assert_equal "example.com", req.uri.host
|
|
520
|
+
assert_equal "/api/users/1", req.uri.path
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
# make_request_uri persistent guard
|
|
524
|
+
|
|
525
|
+
def test_persistent_guard_when_not_persistent_does_not_prepend_origin
|
|
526
|
+
builder = build_builder
|
|
527
|
+
req = builder.build(:get, "http://example.com/path")
|
|
528
|
+
|
|
529
|
+
assert_equal "example.com", req.uri.host
|
|
530
|
+
assert_equal "/path", req.uri.path
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def test_persistent_guard_when_persistent_and_absolute_http_does_not_prepend
|
|
534
|
+
builder = build_builder(persistent: "http://example.com")
|
|
535
|
+
req = builder.build(:get, "http://other.com/path")
|
|
536
|
+
|
|
537
|
+
assert_equal "other.com", req.uri.host
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# make_request_uri returns HTTP::URI
|
|
541
|
+
|
|
542
|
+
def test_make_request_uri_returns_http_uri_not_stdlib_uri
|
|
543
|
+
builder = build_builder
|
|
544
|
+
req = builder.build(:get, "http://example.com/path")
|
|
545
|
+
|
|
546
|
+
assert_instance_of HTTP::URI, req.uri
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# make_request_uri empty path normalization
|
|
550
|
+
|
|
551
|
+
def test_make_request_uri_normalizes_empty_path_to_slash_for_bare_domain
|
|
552
|
+
builder = build_builder
|
|
553
|
+
req = builder.build(:get, "http://example.com")
|
|
554
|
+
|
|
555
|
+
assert_equal "/", req.uri.path
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def test_make_request_uri_does_not_change_non_empty_path
|
|
559
|
+
builder = build_builder
|
|
560
|
+
req = builder.build(:get, "http://example.com/existing")
|
|
561
|
+
|
|
562
|
+
assert_equal "/existing", req.uri.path
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def test_make_request_uri_normalizes_empty_path_when_using_persistent
|
|
566
|
+
opts = HTTP::Options.new(persistent: "http://example.com")
|
|
567
|
+
b = HTTP::Request::Builder.new(opts)
|
|
568
|
+
req = b.build(:get, "http://example.com")
|
|
569
|
+
|
|
570
|
+
assert_equal "/", req.uri.path
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# resolve_against_base String conversion
|
|
574
|
+
|
|
575
|
+
def test_resolve_against_base_string_conversion_resolves_and_returns_valid_uri
|
|
576
|
+
builder = build_builder(base_uri: "http://example.com/api/")
|
|
577
|
+
req = builder.build(:get, "users")
|
|
578
|
+
|
|
579
|
+
assert_instance_of HTTP::URI, req.uri
|
|
580
|
+
assert_equal "/api/users", req.uri.path
|
|
581
|
+
assert_equal "example.com", req.uri.host
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# #build uses HTTP::Request (not Request)
|
|
585
|
+
|
|
586
|
+
def test_build_creates_an_http_request_instance
|
|
587
|
+
builder = build_builder
|
|
588
|
+
req = builder.build(:get, "http://example.com/")
|
|
589
|
+
|
|
590
|
+
assert_instance_of HTTP::Request, req
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# make_form_data type checks
|
|
594
|
+
|
|
595
|
+
def test_make_form_data_with_multipart_subclass_passes_through
|
|
596
|
+
multipart = HTTP::FormData::Multipart.new({ "part" => HTTP::FormData::Part.new("val") })
|
|
597
|
+
opts = HTTP::Options.new(form: multipart)
|
|
598
|
+
b = HTTP::Request::Builder.new(opts)
|
|
599
|
+
req = b.build(:post, "http://example.com/")
|
|
600
|
+
|
|
601
|
+
assert_match(%r{\Amultipart/form-data}, req.headers["Content-Type"])
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
def test_make_form_data_with_urlencoded_subclass_passes_through
|
|
605
|
+
urlencoded = HTTP::FormData::Urlencoded.new({ "a" => "1" })
|
|
606
|
+
opts = HTTP::Options.new(form: urlencoded)
|
|
607
|
+
b = HTTP::Request::Builder.new(opts)
|
|
608
|
+
req = b.build(:post, "http://example.com/")
|
|
609
|
+
|
|
610
|
+
assert_equal "application/x-www-form-urlencoded", req.headers["Content-Type"]
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def test_make_form_data_with_plain_hash_creates_form_data_and_sets_content_type
|
|
614
|
+
opts = HTTP::Options.new(form: { "key" => "value" })
|
|
615
|
+
b = HTTP::Request::Builder.new(opts)
|
|
616
|
+
req = b.build(:post, "http://example.com/")
|
|
617
|
+
|
|
618
|
+
assert_equal "application/x-www-form-urlencoded", req.headers["Content-Type"]
|
|
619
|
+
chunks = req.body.enum_for(:each).map(&:dup)
|
|
620
|
+
|
|
621
|
+
assert_includes chunks.join, "key=value"
|
|
622
|
+
end
|
|
623
|
+
end
|