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,303 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class FormDataMultipartTest < Minitest::Test
|
|
6
|
+
cover "HTTP::FormData::Multipart*"
|
|
7
|
+
|
|
8
|
+
BOUNDARY_PATTERN = /-{21}[a-f0-9]{42}/
|
|
9
|
+
CRLF = "\r\n"
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def fixture_path
|
|
14
|
+
@fixture_path ||= Pathname.new(__dir__).join("fixtures/the-http-gem.info").realpath
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def file
|
|
18
|
+
@file ||= HTTP::FormData::File.new(fixture_path)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def params
|
|
22
|
+
{ foo: :bar, baz: file }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def form_data
|
|
26
|
+
@form_data ||= HTTP::FormData::Multipart.new(params)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def disposition(params)
|
|
30
|
+
params = params.map { |k, v| "#{k}=#{v.inspect}" }.join("; ")
|
|
31
|
+
"Content-Disposition: form-data; #{params}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
public
|
|
35
|
+
|
|
36
|
+
# --- Multipart body generation ---
|
|
37
|
+
|
|
38
|
+
def test_generates_multipart_data
|
|
39
|
+
b = form_data.boundary
|
|
40
|
+
expected = [
|
|
41
|
+
"--#{b}#{CRLF}",
|
|
42
|
+
"#{disposition 'name' => 'foo'}#{CRLF}",
|
|
43
|
+
"#{CRLF}bar#{CRLF}",
|
|
44
|
+
"--#{b}#{CRLF}",
|
|
45
|
+
"#{disposition 'name' => 'baz', 'filename' => file.filename}#{CRLF}",
|
|
46
|
+
"Content-Type: #{file.content_type}#{CRLF}",
|
|
47
|
+
"#{CRLF}#{file}#{CRLF}",
|
|
48
|
+
"--#{b}--#{CRLF}"
|
|
49
|
+
].join
|
|
50
|
+
|
|
51
|
+
assert_equal expected, form_data.to_s
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_user_defined_boundary
|
|
55
|
+
fd = HTTP::FormData::Multipart.new(params, boundary: "my-boundary")
|
|
56
|
+
expected = [
|
|
57
|
+
"--my-boundary#{CRLF}",
|
|
58
|
+
"#{disposition 'name' => 'foo'}#{CRLF}",
|
|
59
|
+
"#{CRLF}bar#{CRLF}",
|
|
60
|
+
"--my-boundary#{CRLF}",
|
|
61
|
+
"#{disposition 'name' => 'baz', 'filename' => file.filename}#{CRLF}",
|
|
62
|
+
"Content-Type: #{file.content_type}#{CRLF}",
|
|
63
|
+
"#{CRLF}#{file}#{CRLF}",
|
|
64
|
+
"--my-boundary--#{CRLF}"
|
|
65
|
+
].join
|
|
66
|
+
|
|
67
|
+
assert_equal expected, fd.to_s
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_part_without_filename
|
|
71
|
+
part = HTTP::FormData::Part.new("s", content_type: "mime/type")
|
|
72
|
+
fd = HTTP::FormData::Multipart.new({ foo: part })
|
|
73
|
+
b = fd.content_type[/(#{BOUNDARY_PATTERN})$/o, 1]
|
|
74
|
+
|
|
75
|
+
expected = [
|
|
76
|
+
"--#{b}#{CRLF}",
|
|
77
|
+
"#{disposition 'name' => 'foo'}#{CRLF}",
|
|
78
|
+
"Content-Type: #{part.content_type}#{CRLF}",
|
|
79
|
+
"#{CRLF}s#{CRLF}",
|
|
80
|
+
"--#{b}--#{CRLF}"
|
|
81
|
+
].join
|
|
82
|
+
|
|
83
|
+
assert_equal expected, fd.to_s
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_part_without_content_type
|
|
87
|
+
part = HTTP::FormData::Part.new("s")
|
|
88
|
+
fd = HTTP::FormData::Multipart.new({ foo: part })
|
|
89
|
+
b = fd.content_type[/(#{BOUNDARY_PATTERN})$/o, 1]
|
|
90
|
+
|
|
91
|
+
expected = [
|
|
92
|
+
"--#{b}#{CRLF}",
|
|
93
|
+
"#{disposition 'name' => 'foo'}#{CRLF}",
|
|
94
|
+
"#{CRLF}s#{CRLF}",
|
|
95
|
+
"--#{b}--#{CRLF}"
|
|
96
|
+
].join
|
|
97
|
+
|
|
98
|
+
assert_equal expected, fd.to_s
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_supports_enumerable_of_pairs
|
|
102
|
+
enum = Enumerator.new { |y| y << %i[foo bar] << %i[foo baz] }
|
|
103
|
+
fd = HTTP::FormData::Multipart.new(enum)
|
|
104
|
+
b = fd.boundary
|
|
105
|
+
|
|
106
|
+
expected = [
|
|
107
|
+
"--#{b}#{CRLF}",
|
|
108
|
+
"#{disposition 'name' => 'foo'}#{CRLF}",
|
|
109
|
+
"#{CRLF}bar#{CRLF}",
|
|
110
|
+
"--#{b}#{CRLF}",
|
|
111
|
+
"#{disposition 'name' => 'foo'}#{CRLF}",
|
|
112
|
+
"#{CRLF}baz#{CRLF}",
|
|
113
|
+
"--#{b}--#{CRLF}"
|
|
114
|
+
].join
|
|
115
|
+
|
|
116
|
+
assert_equal expected, fd.to_s
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_array_of_pairs_with_duplicate_names
|
|
120
|
+
data = [
|
|
121
|
+
["metadata", %(filename="first.txt")],
|
|
122
|
+
["file", HTTP::FormData::File.new(StringIO.new("uno"), content_type: "plain/text", filename: "abc")],
|
|
123
|
+
["metadata", %(filename="second.txt")],
|
|
124
|
+
["file", HTTP::FormData::File.new(StringIO.new("dos"), content_type: "plain/text", filename: "xyz")],
|
|
125
|
+
["metadata", %w[question=why question=not]]
|
|
126
|
+
]
|
|
127
|
+
fd = HTTP::FormData::Multipart.new(data)
|
|
128
|
+
b = fd.boundary
|
|
129
|
+
|
|
130
|
+
expected = [
|
|
131
|
+
%(--#{b}\r\n),
|
|
132
|
+
%(Content-Disposition: form-data; name="metadata"\r\n),
|
|
133
|
+
%(\r\nfilename="first.txt"\r\n),
|
|
134
|
+
%(--#{b}\r\n),
|
|
135
|
+
%(Content-Disposition: form-data; name="file"; filename="abc"\r\n),
|
|
136
|
+
%(Content-Type: plain/text\r\n),
|
|
137
|
+
%(\r\nuno\r\n),
|
|
138
|
+
%(--#{b}\r\n),
|
|
139
|
+
%(Content-Disposition: form-data; name="metadata"\r\n),
|
|
140
|
+
%(\r\nfilename="second.txt"\r\n),
|
|
141
|
+
%(--#{b}\r\n),
|
|
142
|
+
%(Content-Disposition: form-data; name="file"; filename="xyz"\r\n),
|
|
143
|
+
%(Content-Type: plain/text\r\n),
|
|
144
|
+
%(\r\ndos\r\n),
|
|
145
|
+
%(--#{b}\r\n),
|
|
146
|
+
%(Content-Disposition: form-data; name="metadata"\r\n),
|
|
147
|
+
%(\r\nquestion=why\r\n),
|
|
148
|
+
%(--#{b}\r\n),
|
|
149
|
+
%(Content-Disposition: form-data; name="metadata"\r\n),
|
|
150
|
+
%(\r\nquestion=not\r\n),
|
|
151
|
+
%(--#{b}--\r\n)
|
|
152
|
+
].join
|
|
153
|
+
|
|
154
|
+
assert_equal expected, fd.to_s
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# --- size / read / rewind ---
|
|
158
|
+
|
|
159
|
+
def test_size_returns_bytesize
|
|
160
|
+
assert_equal form_data.to_s.bytesize, form_data.size
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def test_read_returns_multipart_data
|
|
164
|
+
assert_equal form_data.to_s, form_data.read
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def test_rewind
|
|
168
|
+
form_data.read
|
|
169
|
+
form_data.rewind
|
|
170
|
+
|
|
171
|
+
assert_equal form_data.to_s, form_data.read
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def test_content_length
|
|
175
|
+
assert_equal form_data.to_s.bytesize, form_data.content_length
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def test_to_s_rewinds_content
|
|
179
|
+
content = form_data.read
|
|
180
|
+
|
|
181
|
+
assert_equal content, form_data.to_s
|
|
182
|
+
assert_equal content, form_data.read
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# --- Content type ---
|
|
186
|
+
|
|
187
|
+
def test_content_type_matches_pattern
|
|
188
|
+
assert_match(%r{^multipart/form-data; boundary=#{BOUNDARY_PATTERN}$}o, form_data.content_type)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def test_content_type_with_user_defined_boundary
|
|
192
|
+
fd = HTTP::FormData::Multipart.new(params, boundary: "my-boundary")
|
|
193
|
+
|
|
194
|
+
assert_equal "multipart/form-data; boundary=my-boundary", fd.content_type
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def test_content_type_with_custom_type
|
|
198
|
+
fd = HTTP::FormData::Multipart.new(params, boundary: "b", content_type: "multipart/related")
|
|
199
|
+
|
|
200
|
+
assert_equal "multipart/related; boundary=b", fd.content_type
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def test_content_type_with_multipart_mixed
|
|
204
|
+
fd = HTTP::FormData::Multipart.new(params, boundary: "b", content_type: "multipart/mixed")
|
|
205
|
+
|
|
206
|
+
assert_equal "multipart/mixed; boundary=b", fd.content_type
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def test_content_type_default_is_form_data
|
|
210
|
+
assert_equal "multipart/form-data", HTTP::FormData::Multipart::DEFAULT_CONTENT_TYPE
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_content_type_converts_to_string
|
|
214
|
+
fd = HTTP::FormData::Multipart.new(params, boundary: "b", content_type: :"multipart/related")
|
|
215
|
+
|
|
216
|
+
assert_equal "multipart/related; boundary=b", fd.content_type
|
|
217
|
+
assert_instance_of String, fd.content_type
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# --- Boundary ---
|
|
221
|
+
|
|
222
|
+
def test_boundary_matches_pattern
|
|
223
|
+
assert_match(BOUNDARY_PATTERN, form_data.boundary)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def test_boundary_with_user_defined_value
|
|
227
|
+
fd = HTTP::FormData::Multipart.new(params, boundary: "my-boundary")
|
|
228
|
+
|
|
229
|
+
assert_equal "my-boundary", fd.boundary
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def test_boundary_is_frozen_string
|
|
233
|
+
assert_predicate form_data.boundary, :frozen?
|
|
234
|
+
assert_instance_of String, form_data.boundary
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def test_boundary_with_symbol_value
|
|
238
|
+
fd = HTTP::FormData::Multipart.new({ foo: "bar" }, boundary: :"my-sym-boundary")
|
|
239
|
+
|
|
240
|
+
assert_equal "my-sym-boundary", fd.boundary
|
|
241
|
+
assert_instance_of String, fd.boundary
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def test_generate_boundary
|
|
245
|
+
assert_match(BOUNDARY_PATTERN, HTTP::FormData::Multipart.generate_boundary)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# --- Param ---
|
|
249
|
+
|
|
250
|
+
def test_parts_with_nil_data
|
|
251
|
+
form = HTTP::FormData::Multipart.new(nil, boundary: "test-boundary")
|
|
252
|
+
|
|
253
|
+
assert_equal "--test-boundary--\r\n", form.to_s
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def test_param_converts_name_to_string
|
|
257
|
+
part = HTTP::FormData::Part.new("val")
|
|
258
|
+
body = HTTP::FormData::Multipart.new({ 123 => part }, boundary: "b").to_s
|
|
259
|
+
|
|
260
|
+
assert_includes body, 'name="123"'
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def test_param_wraps_non_part_value
|
|
264
|
+
body = HTTP::FormData::Multipart.new({ foo: "raw_string" }, boundary: "b").to_s
|
|
265
|
+
|
|
266
|
+
assert_includes body, "raw_string"
|
|
267
|
+
refute_includes body, "Content-Type:"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def test_param_uses_part_directly
|
|
271
|
+
part = HTTP::FormData::Part.new("part_body", content_type: "text/plain")
|
|
272
|
+
body = HTTP::FormData::Multipart.new({ foo: part }, boundary: "b").to_s
|
|
273
|
+
|
|
274
|
+
assert_includes body, "part_body"
|
|
275
|
+
assert_includes body, "Content-Type: text/plain"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def test_param_header_contains_content_disposition
|
|
279
|
+
body = HTTP::FormData::Multipart.new({ myfield: "val" }, boundary: "b").to_s
|
|
280
|
+
|
|
281
|
+
assert_includes body, "Content-Disposition: form-data; name=\"myfield\""
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def test_param_includes_content_type_when_present
|
|
285
|
+
part = HTTP::FormData::Part.new("val", content_type: "application/json")
|
|
286
|
+
body = HTTP::FormData::Multipart.new({ f: part }, boundary: "b").to_s
|
|
287
|
+
|
|
288
|
+
assert_includes body, "Content-Type: application/json\r\n"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def test_param_excludes_content_type_when_nil
|
|
292
|
+
part = HTTP::FormData::Part.new("val")
|
|
293
|
+
body = HTTP::FormData::Multipart.new({ f: part }, boundary: "b").to_s
|
|
294
|
+
|
|
295
|
+
refute_includes body, "Content-Type:"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def test_param_footer_is_crlf
|
|
299
|
+
body = HTTP::FormData::Multipart.new({ f: "v" }, boundary: "b").to_s
|
|
300
|
+
|
|
301
|
+
assert_includes body, "v\r\n--b--"
|
|
302
|
+
end
|
|
303
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class FormDataPartTest < Minitest::Test
|
|
6
|
+
cover "HTTP::FormData::Part*"
|
|
7
|
+
|
|
8
|
+
def test_size
|
|
9
|
+
assert_equal 20, HTTP::FormData::Part.new("привет мир!").size
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_to_s
|
|
13
|
+
assert_equal "привет мир!", HTTP::FormData::Part.new("привет мир!").to_s
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_to_s_rewinds_content
|
|
17
|
+
part = HTTP::FormData::Part.new("привет мир!")
|
|
18
|
+
part.to_s
|
|
19
|
+
content = part.read
|
|
20
|
+
|
|
21
|
+
assert_equal content, part.to_s
|
|
22
|
+
assert_equal content, part.read
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_read
|
|
26
|
+
assert_equal "привет мир!", HTTP::FormData::Part.new("привет мир!").read
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_read_with_length
|
|
30
|
+
part = HTTP::FormData::Part.new("hello world")
|
|
31
|
+
|
|
32
|
+
assert_equal "hello", part.read(5)
|
|
33
|
+
assert_equal " worl", part.read(5)
|
|
34
|
+
assert_equal "d", part.read(5)
|
|
35
|
+
assert_nil part.read(5)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_read_with_nil_length
|
|
39
|
+
assert_equal "hello", HTTP::FormData::Part.new("hello").read(nil)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_read_with_outbuf
|
|
43
|
+
part = HTTP::FormData::Part.new("hello")
|
|
44
|
+
buf = +""
|
|
45
|
+
result = part.read(3, buf)
|
|
46
|
+
|
|
47
|
+
assert_equal "hel", result
|
|
48
|
+
assert_equal "hel", buf
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_rewind
|
|
52
|
+
part = HTTP::FormData::Part.new("привет мир!")
|
|
53
|
+
part.read
|
|
54
|
+
part.rewind
|
|
55
|
+
|
|
56
|
+
assert_equal "привет мир!", part.read
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_filename_defaults_to_nil
|
|
60
|
+
assert_nil HTTP::FormData::Part.new("").filename
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_filename_with_option
|
|
64
|
+
assert_equal "foobar.txt", HTTP::FormData::Part.new("", filename: "foobar.txt").filename
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_filename_stores_exact_value
|
|
68
|
+
assert_equal "test.txt", HTTP::FormData::Part.new("body", filename: "test.txt").filename
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_content_type_defaults_to_nil
|
|
72
|
+
assert_nil HTTP::FormData::Part.new("").content_type
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_content_type_with_option
|
|
76
|
+
assert_equal "application/json", HTTP::FormData::Part.new("", content_type: "application/json").content_type
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_content_type_stores_exact_value
|
|
80
|
+
assert_equal "text/plain", HTTP::FormData::Part.new("body", content_type: "text/plain").content_type
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def test_initialize_converts_body_to_string
|
|
84
|
+
assert_equal "42", HTTP::FormData::Part.new(42).to_s
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_initialize_with_symbol_body
|
|
88
|
+
assert_equal "hello", HTTP::FormData::Part.new(:hello).to_s
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class FormDataUrlencodedTest < Minitest::Test
|
|
6
|
+
cover "HTTP::FormData::Urlencoded*"
|
|
7
|
+
|
|
8
|
+
def test_raises_error_for_non_enumerable_input
|
|
9
|
+
assert_raises(HTTP::FormData::Error) { HTTP::FormData::Urlencoded.new(42) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_raises_argument_error_for_non_hash_top_level
|
|
13
|
+
assert_raises(ArgumentError) { HTTP::FormData::Urlencoded.encoder.call(42) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_supports_enumerables_of_pairs
|
|
17
|
+
form_data = HTTP::FormData::Urlencoded.new([%w[foo bar], ["foo", %w[baz moo]]])
|
|
18
|
+
|
|
19
|
+
assert_equal "foo=bar&foo[]=baz&foo[]=moo", form_data.to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_content_type
|
|
23
|
+
assert_equal "application/x-www-form-urlencoded", HTTP::FormData::Urlencoded.new({ foo: "bar" }).content_type
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_content_length
|
|
27
|
+
form_data = HTTP::FormData::Urlencoded.new({ "foo[bar]" => "test" })
|
|
28
|
+
|
|
29
|
+
assert_equal form_data.to_s.bytesize, form_data.content_length
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_content_length_with_unicode
|
|
33
|
+
form_data = HTTP::FormData::Urlencoded.new({ "foo[bar]" => "тест" })
|
|
34
|
+
|
|
35
|
+
assert_equal form_data.to_s.bytesize, form_data.content_length
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_to_s
|
|
39
|
+
assert_equal "foo%5Bbar%5D=test", HTTP::FormData::Urlencoded.new({ "foo[bar]" => "test" }).to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_to_s_with_unicode
|
|
43
|
+
assert_equal "foo%5Bbar%5D=%D1%82%D0%B5%D1%81%D1%82",
|
|
44
|
+
HTTP::FormData::Urlencoded.new({ "foo[bar]" => "тест" }).to_s
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_to_s_with_nested_hashes
|
|
48
|
+
assert_equal "foo[bar]=test", HTTP::FormData::Urlencoded.new({ "foo" => { "bar" => "test" } }).to_s
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_to_s_with_nil_value
|
|
52
|
+
assert_equal "foo", HTTP::FormData::Urlencoded.new({ "foo" => nil }).to_s
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_to_s_rewinds_content
|
|
56
|
+
form_data = HTTP::FormData::Urlencoded.new({ "foo[bar]" => "test" })
|
|
57
|
+
content = form_data.read
|
|
58
|
+
|
|
59
|
+
assert_equal content, form_data.to_s
|
|
60
|
+
assert_equal content, form_data.read
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_size
|
|
64
|
+
form_data = HTTP::FormData::Urlencoded.new({ "foo[bar]" => "test" })
|
|
65
|
+
|
|
66
|
+
assert_equal form_data.to_s.bytesize, form_data.size
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_read
|
|
70
|
+
form_data = HTTP::FormData::Urlencoded.new({ "foo[bar]" => "test" })
|
|
71
|
+
|
|
72
|
+
assert_equal form_data.to_s, form_data.read
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_rewind
|
|
76
|
+
form_data = HTTP::FormData::Urlencoded.new({ "foo[bar]" => "test" })
|
|
77
|
+
form_data.read
|
|
78
|
+
form_data.rewind
|
|
79
|
+
|
|
80
|
+
assert_equal form_data.to_s, form_data.read
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# --- Custom encoders ---
|
|
84
|
+
|
|
85
|
+
def test_custom_class_level_encoder
|
|
86
|
+
original_encoder = HTTP::FormData::Urlencoded.encoder
|
|
87
|
+
HTTP::FormData::Urlencoded.encoder = JSON.method(:dump)
|
|
88
|
+
form_data = HTTP::FormData::Urlencoded.new({ "foo[bar]" => "test" })
|
|
89
|
+
|
|
90
|
+
assert_equal '{"foo[bar]":"test"}', form_data.to_s
|
|
91
|
+
ensure
|
|
92
|
+
HTTP::FormData::Urlencoded.encoder = original_encoder
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_encoder_rejects_non_callable
|
|
96
|
+
assert_raises(ArgumentError) { HTTP::FormData::Urlencoded.encoder = "not callable" }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def test_custom_instance_level_encoder
|
|
100
|
+
encoder = proc { |data| JSON.dump(data) }
|
|
101
|
+
form_data = HTTP::FormData::Urlencoded.new({ "foo[bar]" => "test" }, encoder: encoder)
|
|
102
|
+
|
|
103
|
+
assert_equal '{"foo[bar]":"test"}', form_data.to_s
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_default_encoder_returns_callable
|
|
107
|
+
assert_respond_to HTTP::FormData::Urlencoded.encoder, :call
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_default_encoder_encodes_correctly
|
|
111
|
+
assert_equal "key=value", HTTP::FormData::Urlencoded.encoder.call({ "key" => "value" })
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_nil_encoder_uses_class_default
|
|
115
|
+
form_data = HTTP::FormData::Urlencoded.new({ foo: "bar" }, encoder: nil)
|
|
116
|
+
|
|
117
|
+
assert_equal "foo=bar", form_data.to_s
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_custom_encoder_is_called
|
|
121
|
+
calls = []
|
|
122
|
+
custom = proc { |data|
|
|
123
|
+
calls << data
|
|
124
|
+
"custom"
|
|
125
|
+
}
|
|
126
|
+
form_data = HTTP::FormData::Urlencoded.new({ a: "b" }, encoder: custom)
|
|
127
|
+
|
|
128
|
+
assert_equal "custom", form_data.to_s
|
|
129
|
+
refute_empty calls
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# --- Initialize edge cases ---
|
|
133
|
+
|
|
134
|
+
def test_initialize_with_nil_data
|
|
135
|
+
assert_equal "", HTTP::FormData::Urlencoded.new(nil).to_s
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_initialize_with_to_h_object
|
|
139
|
+
obj = Object.new
|
|
140
|
+
def obj.to_h = { x: "y" }
|
|
141
|
+
|
|
142
|
+
assert_equal "x=y", HTTP::FormData::Urlencoded.new(obj).to_s
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_initialize_stores_encoded_content
|
|
146
|
+
form_data = HTTP::FormData::Urlencoded.new({ a: "1", b: "2" })
|
|
147
|
+
|
|
148
|
+
assert_equal "a=1&b=2", form_data.to_s
|
|
149
|
+
assert_equal 7, form_data.size
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_read_with_length
|
|
153
|
+
form_data = HTTP::FormData::Urlencoded.new({ foo: "bar" })
|
|
154
|
+
|
|
155
|
+
assert_equal "fo", form_data.read(2)
|
|
156
|
+
assert_equal "o=b", form_data.read(3)
|
|
157
|
+
assert_equal "ar", form_data.read(5)
|
|
158
|
+
assert_nil form_data.read(1)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def test_read_with_nil_length
|
|
162
|
+
assert_equal "foo=bar", HTTP::FormData::Urlencoded.new({ foo: "bar" }).read(nil)
|
|
163
|
+
end
|
|
164
|
+
end
|