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,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPFeaturesRaiseErrorTest < Minitest::Test
|
|
6
|
+
cover "HTTP::Features::RaiseError*"
|
|
7
|
+
|
|
8
|
+
def connection
|
|
9
|
+
@connection ||= fake
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def build_response(status:)
|
|
13
|
+
HTTP::Response.new(
|
|
14
|
+
version: "1.1",
|
|
15
|
+
status: status,
|
|
16
|
+
headers: {},
|
|
17
|
+
connection: connection,
|
|
18
|
+
request: HTTP::Request.new(verb: :get, uri: "https://example.com")
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# -- #wrap_response --
|
|
23
|
+
|
|
24
|
+
def test_wrap_response_when_status_is_200_returns_original_response
|
|
25
|
+
feature = HTTP::Features::RaiseError.new(ignore: [])
|
|
26
|
+
response = build_response(status: 200)
|
|
27
|
+
result = feature.wrap_response(response)
|
|
28
|
+
|
|
29
|
+
assert_same response, result
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_wrap_response_when_status_is_399_returns_original_response
|
|
33
|
+
feature = HTTP::Features::RaiseError.new(ignore: [])
|
|
34
|
+
response = build_response(status: 399)
|
|
35
|
+
result = feature.wrap_response(response)
|
|
36
|
+
|
|
37
|
+
assert_same response, result
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_wrap_response_when_status_is_400_raises
|
|
41
|
+
feature = HTTP::Features::RaiseError.new(ignore: [])
|
|
42
|
+
response = build_response(status: 400)
|
|
43
|
+
err = assert_raises(HTTP::StatusError) { feature.wrap_response(response) }
|
|
44
|
+
assert_equal "Unexpected status code 400", err.message
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_wrap_response_when_status_is_599_raises
|
|
48
|
+
feature = HTTP::Features::RaiseError.new(ignore: [])
|
|
49
|
+
response = build_response(status: 599)
|
|
50
|
+
err = assert_raises(HTTP::StatusError) { feature.wrap_response(response) }
|
|
51
|
+
assert_equal "Unexpected status code 599", err.message
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_wrap_response_when_error_status_is_ignored_returns_original_response
|
|
55
|
+
feature = HTTP::Features::RaiseError.new(ignore: [500])
|
|
56
|
+
response = build_response(status: 500)
|
|
57
|
+
result = feature.wrap_response(response)
|
|
58
|
+
|
|
59
|
+
assert_same response, result
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# -- #initialize --
|
|
63
|
+
|
|
64
|
+
def test_initialize_defaults_ignore_to_empty_array
|
|
65
|
+
feature = HTTP::Features::RaiseError.new
|
|
66
|
+
response = HTTP::Response.new(
|
|
67
|
+
version: "1.1", status: 500, headers: {},
|
|
68
|
+
connection: connection,
|
|
69
|
+
request: HTTP::Request.new(verb: :get, uri: "https://example.com")
|
|
70
|
+
)
|
|
71
|
+
assert_raises(HTTP::StatusError) { feature.wrap_response(response) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_initialize_is_a_feature
|
|
75
|
+
assert_kind_of HTTP::Feature, HTTP::Features::RaiseError.new
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class FormDataCompositeIOTest < Minitest::Test
|
|
6
|
+
cover "HTTP::FormData::CompositeIO*"
|
|
7
|
+
|
|
8
|
+
def setup
|
|
9
|
+
@ios = ["Hello", " ", "", "world", "!"].map { |s| StringIO.new(s) }
|
|
10
|
+
@composite_io = HTTP::FormData::CompositeIO.new(@ios)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_accepts_ios_and_strings
|
|
14
|
+
io = HTTP::FormData::CompositeIO.new(["Hello ", StringIO.new("world!")])
|
|
15
|
+
|
|
16
|
+
assert_equal "Hello world!", io.read
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_rejects_invalid_io_types
|
|
20
|
+
error = assert_raises(ArgumentError) { HTTP::FormData::CompositeIO.new(%i[hello world]) }
|
|
21
|
+
|
|
22
|
+
assert_includes error.message, ":hello"
|
|
23
|
+
assert_includes error.message, "is neither a String nor an IO object"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_error_message_contains_inspect
|
|
27
|
+
obj = Object.new
|
|
28
|
+
def obj.inspect = "INVALID_IO_INSPECT"
|
|
29
|
+
def obj.to_s = "INVALID_IO_TO_S"
|
|
30
|
+
|
|
31
|
+
error = assert_raises(ArgumentError) { HTTP::FormData::CompositeIO.new([obj]) }
|
|
32
|
+
|
|
33
|
+
assert_includes error.message, "INVALID_IO_INSPECT"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_reads_all_data
|
|
37
|
+
assert_equal "Hello world!", @composite_io.read
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_reads_partial_data
|
|
41
|
+
assert_equal "Hel", @composite_io.read(3)
|
|
42
|
+
assert_equal "lo", @composite_io.read(2)
|
|
43
|
+
assert_equal " ", @composite_io.read(1)
|
|
44
|
+
assert_equal "world!", @composite_io.read(6)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_returns_empty_string_when_exhausted_without_length
|
|
48
|
+
@composite_io.read
|
|
49
|
+
|
|
50
|
+
assert_equal "", @composite_io.read
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_returns_nil_when_exhausted_with_length
|
|
54
|
+
@composite_io.read
|
|
55
|
+
|
|
56
|
+
assert_nil @composite_io.read(3)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_reads_partial_data_with_buffer
|
|
60
|
+
outbuf = +""
|
|
61
|
+
|
|
62
|
+
assert_equal "Hel", @composite_io.read(3, outbuf)
|
|
63
|
+
assert_equal "lo", @composite_io.read(2, outbuf)
|
|
64
|
+
assert_equal " ", @composite_io.read(1, outbuf)
|
|
65
|
+
assert_equal "world!", @composite_io.read(6, outbuf)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_fills_buffer_with_retrieved_content
|
|
69
|
+
outbuf = +""
|
|
70
|
+
@composite_io.read(3, outbuf)
|
|
71
|
+
|
|
72
|
+
assert_equal "Hel", outbuf
|
|
73
|
+
@composite_io.read(2, outbuf)
|
|
74
|
+
|
|
75
|
+
assert_equal "lo", outbuf
|
|
76
|
+
@composite_io.read(1, outbuf)
|
|
77
|
+
|
|
78
|
+
assert_equal " ", outbuf
|
|
79
|
+
@composite_io.read(6, outbuf)
|
|
80
|
+
|
|
81
|
+
assert_equal "world!", outbuf
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_clears_buffer_when_exhausted_with_length
|
|
85
|
+
outbuf = +"content"
|
|
86
|
+
@composite_io.read
|
|
87
|
+
|
|
88
|
+
assert_nil @composite_io.read(3, outbuf)
|
|
89
|
+
assert_equal "", outbuf
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_returns_binary_encoding
|
|
93
|
+
io = HTTP::FormData::CompositeIO.new(%w[Janko Marohnić])
|
|
94
|
+
|
|
95
|
+
assert_equal Encoding::BINARY, io.read(5).encoding
|
|
96
|
+
assert_equal Encoding::BINARY, io.read(9).encoding
|
|
97
|
+
|
|
98
|
+
io.rewind
|
|
99
|
+
|
|
100
|
+
assert_equal Encoding::BINARY, io.read.encoding
|
|
101
|
+
assert_equal Encoding::BINARY, io.read.encoding
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_reads_data_in_bytes
|
|
105
|
+
emoji = "😃"
|
|
106
|
+
io = HTTP::FormData::CompositeIO.new([emoji])
|
|
107
|
+
|
|
108
|
+
assert_equal emoji.b[0], io.read(1)
|
|
109
|
+
assert_equal emoji.b[1], io.read(1)
|
|
110
|
+
assert_equal emoji.b[2], io.read(1)
|
|
111
|
+
assert_equal emoji.b[3], io.read(1)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_rewinds_all_ios
|
|
115
|
+
@composite_io.read
|
|
116
|
+
@composite_io.rewind
|
|
117
|
+
|
|
118
|
+
assert_equal "Hello world!", @composite_io.read
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_size_returns_sum_of_all_ios
|
|
122
|
+
assert_equal 12, @composite_io.size
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_size_returns_zero_for_empty
|
|
126
|
+
assert_equal 0, HTTP::FormData::CompositeIO.new([]).size
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_accepts_string_subclass
|
|
130
|
+
io = HTTP::FormData::CompositeIO.new([Class.new(String).new("hello")])
|
|
131
|
+
|
|
132
|
+
assert_equal "hello", io.read
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_accepts_custom_io_object
|
|
136
|
+
custom_io = Class.new do
|
|
137
|
+
def initialize = @done = false
|
|
138
|
+
|
|
139
|
+
def read(length = nil, outbuf = nil)
|
|
140
|
+
if @done
|
|
141
|
+
length ? nil : ""
|
|
142
|
+
else
|
|
143
|
+
@done = true
|
|
144
|
+
result = +"custom"
|
|
145
|
+
outbuf ? outbuf.replace(result) : result
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def size = 6
|
|
150
|
+
def rewind = @done = false
|
|
151
|
+
end.new
|
|
152
|
+
|
|
153
|
+
assert_equal "custom", HTTP::FormData::CompositeIO.new([custom_io]).read
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_starts_reading_from_beginning
|
|
157
|
+
assert_equal "a", HTTP::FormData::CompositeIO.new(%w[abc def]).read(1)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_reads_across_io_boundaries
|
|
161
|
+
io = HTTP::FormData::CompositeIO.new(%w[abc def])
|
|
162
|
+
|
|
163
|
+
assert_equal "ab", io.read(2)
|
|
164
|
+
assert_equal "cd", io.read(2)
|
|
165
|
+
assert_equal "ef", io.read(2)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_skips_empty_io_in_middle
|
|
169
|
+
assert_equal "abcd", HTTP::FormData::CompositeIO.new(["ab", "", "cd"]).read
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def test_respects_length_exactly
|
|
173
|
+
io = HTTP::FormData::CompositeIO.new(%w[abcdef ghijkl])
|
|
174
|
+
|
|
175
|
+
assert_equal "abc", io.read(3)
|
|
176
|
+
assert_equal "def", io.read(3)
|
|
177
|
+
assert_equal "ghi", io.read(3)
|
|
178
|
+
assert_equal "jkl", io.read(3)
|
|
179
|
+
assert_nil io.read(1)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def test_length_spanning_ios
|
|
183
|
+
io = HTTP::FormData::CompositeIO.new(%w[ab cd ef])
|
|
184
|
+
|
|
185
|
+
assert_equal "abcd", io.read(4)
|
|
186
|
+
assert_equal "ef", io.read(4)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def test_read_all_with_outbuf
|
|
190
|
+
outbuf = +""
|
|
191
|
+
io = HTTP::FormData::CompositeIO.new(["hello", " ", "world"])
|
|
192
|
+
result = io.read(nil, outbuf)
|
|
193
|
+
|
|
194
|
+
assert_equal "hello world", result
|
|
195
|
+
assert_equal "hello world", outbuf
|
|
196
|
+
assert_same result, outbuf
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def test_read_with_outbuf_clears_previous_content
|
|
200
|
+
outbuf = +"previous content"
|
|
201
|
+
io = HTTP::FormData::CompositeIO.new(["new"])
|
|
202
|
+
io.read(nil, outbuf)
|
|
203
|
+
|
|
204
|
+
assert_equal "new", outbuf
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def test_outbuf_encoding_forced_to_binary
|
|
208
|
+
outbuf = +"hello"
|
|
209
|
+
outbuf.force_encoding(Encoding::UTF_8)
|
|
210
|
+
io = HTTP::FormData::CompositeIO.new(%w[Marohnić])
|
|
211
|
+
io.read(5, outbuf)
|
|
212
|
+
|
|
213
|
+
assert_equal Encoding::BINARY, outbuf.encoding
|
|
214
|
+
end
|
|
215
|
+
end
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class FormDataFileTest < Minitest::Test
|
|
6
|
+
cover "HTTP::FormData::File*"
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def fixture_path
|
|
11
|
+
@fixture_path ||= Pathname.new(__dir__).join("fixtures/the-http-gem.info").realpath
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def fixture_content
|
|
15
|
+
@fixture_content ||= fixture_path.read(mode: "rb")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Yields [form_file, label, expected_content] for each IO source type.
|
|
19
|
+
# Handles closing File IOs in an ensure block.
|
|
20
|
+
def each_source
|
|
21
|
+
yield HTTP::FormData::File.new(fixture_path.to_s), "string_path", fixture_content
|
|
22
|
+
yield HTTP::FormData::File.new(fixture_path), "pathname", fixture_content
|
|
23
|
+
|
|
24
|
+
file = fixture_path.open("rb")
|
|
25
|
+
begin
|
|
26
|
+
yield HTTP::FormData::File.new(file), "file", fixture_content
|
|
27
|
+
ensure
|
|
28
|
+
file.close
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
yield HTTP::FormData::File.new(StringIO.new("привет мир!")), "string_io", "привет мир!"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
public
|
|
35
|
+
|
|
36
|
+
# --- Core Readable behavior across IO sources ---
|
|
37
|
+
|
|
38
|
+
def test_size
|
|
39
|
+
each_source do |form_file, label, content|
|
|
40
|
+
assert_equal content.bytesize, form_file.size, "size failed for #{label}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_to_s
|
|
45
|
+
each_source do |form_file, label, content|
|
|
46
|
+
assert_equal content, form_file.to_s, "to_s failed for #{label}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_to_s_rewinds_content
|
|
51
|
+
each_source do |form_file, label, _content|
|
|
52
|
+
content = form_file.read
|
|
53
|
+
|
|
54
|
+
assert_equal content, form_file.to_s, "to_s rewind failed for #{label}"
|
|
55
|
+
assert_equal content, form_file.read, "read after to_s failed for #{label}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_read
|
|
60
|
+
each_source do |form_file, label, content|
|
|
61
|
+
assert_equal content, form_file.read, "read failed for #{label}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_rewind
|
|
66
|
+
each_source do |form_file, label, _content|
|
|
67
|
+
content = form_file.read
|
|
68
|
+
form_file.rewind
|
|
69
|
+
|
|
70
|
+
assert_equal content, form_file.read, "rewind failed for #{label}"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# --- Filename detection ---
|
|
75
|
+
|
|
76
|
+
def test_filename_with_string_path
|
|
77
|
+
assert_equal "the-http-gem.info", HTTP::FormData::File.new(fixture_path.to_s).filename
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_filename_with_string_path_and_option
|
|
81
|
+
assert_equal "foobar.txt", HTTP::FormData::File.new(fixture_path.to_s, filename: "foobar.txt").filename
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_filename_with_pathname
|
|
85
|
+
assert_equal "the-http-gem.info", HTTP::FormData::File.new(fixture_path).filename
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_filename_with_pathname_and_option
|
|
89
|
+
assert_equal "foobar.txt", HTTP::FormData::File.new(fixture_path, filename: "foobar.txt").filename
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_filename_with_file
|
|
93
|
+
file = fixture_path.open
|
|
94
|
+
|
|
95
|
+
assert_equal "the-http-gem.info", HTTP::FormData::File.new(file).filename
|
|
96
|
+
ensure
|
|
97
|
+
file.close
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_filename_with_file_and_option
|
|
101
|
+
file = fixture_path.open
|
|
102
|
+
|
|
103
|
+
assert_equal "foobar.txt", HTTP::FormData::File.new(file, filename: "foobar.txt").filename
|
|
104
|
+
ensure
|
|
105
|
+
file.close
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_filename_with_io
|
|
109
|
+
io = StringIO.new
|
|
110
|
+
|
|
111
|
+
assert_equal "stream-#{io.object_id}", HTTP::FormData::File.new(io).filename
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_filename_with_io_and_option
|
|
115
|
+
assert_equal "foobar.txt", HTTP::FormData::File.new(StringIO.new, filename: "foobar.txt").filename
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Kill: ::File.basename(io.path) replaced with ::File.basename(io)
|
|
119
|
+
def test_filename_for_io_with_path_method
|
|
120
|
+
io = StringIO.new("data")
|
|
121
|
+
io.define_singleton_method(:path) { "/some/dir/custom.txt" }
|
|
122
|
+
|
|
123
|
+
assert_equal "custom.txt", HTTP::FormData::File.new(io).filename
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_filename_for_io_without_path
|
|
127
|
+
io = StringIO.new("data")
|
|
128
|
+
form_file = HTTP::FormData::File.new(io)
|
|
129
|
+
|
|
130
|
+
assert_equal "stream-#{io.object_id}", form_file.filename
|
|
131
|
+
assert_match(/\Astream-\d+\z/, form_file.filename)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# --- Content type ---
|
|
135
|
+
|
|
136
|
+
def test_content_type_default
|
|
137
|
+
assert_equal HTTP::FormData::File::DEFAULT_MIME, HTTP::FormData::File.new(StringIO.new).content_type
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_content_type_with_option
|
|
141
|
+
assert_equal "application/json",
|
|
142
|
+
HTTP::FormData::File.new(StringIO.new, content_type: "application/json").content_type
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_content_type_converts_to_string
|
|
146
|
+
form_file = HTTP::FormData::File.new(StringIO.new("data"), content_type: :json)
|
|
147
|
+
|
|
148
|
+
assert_equal "json", form_file.content_type
|
|
149
|
+
assert_instance_of String, form_file.content_type
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# --- make_io ---
|
|
153
|
+
|
|
154
|
+
def test_make_io_with_string_subclass
|
|
155
|
+
assert_equal fixture_content, HTTP::FormData::File.new(Class.new(String).new(fixture_path.to_s)).to_s
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def test_make_io_with_pathname_subclass
|
|
159
|
+
assert_equal fixture_content, HTTP::FormData::File.new(Class.new(Pathname).new(fixture_path.to_s)).to_s
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_string_path_opens_in_binmode
|
|
163
|
+
assert_equal Encoding::ASCII_8BIT, HTTP::FormData::File.new(fixture_path.to_s).read.encoding
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def test_pathname_opens_in_binmode
|
|
167
|
+
assert_equal Encoding::ASCII_8BIT, HTTP::FormData::File.new(fixture_path).read.encoding
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# --- initialize edge cases ---
|
|
171
|
+
|
|
172
|
+
def test_initialize_with_nil_opts
|
|
173
|
+
assert_equal "application/octet-stream", HTTP::FormData::File.new(StringIO.new("data"), nil).content_type
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# --- Readable#read with length/outbuf ---
|
|
177
|
+
|
|
178
|
+
def test_read_with_length
|
|
179
|
+
assert_equal "hello", HTTP::FormData::File.new(StringIO.new("hello world")).read(5)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def test_read_with_nil_length
|
|
183
|
+
assert_equal "hello world", HTTP::FormData::File.new(StringIO.new("hello world")).read(nil)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_read_with_length_and_outbuf
|
|
187
|
+
form_file = HTTP::FormData::File.new(StringIO.new("hello world"))
|
|
188
|
+
outbuf = +""
|
|
189
|
+
result = form_file.read(5, outbuf)
|
|
190
|
+
|
|
191
|
+
assert_equal "hello", result
|
|
192
|
+
assert_equal "hello", outbuf
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def test_read_after_eof_returns_nil_with_length
|
|
196
|
+
form_file = HTTP::FormData::File.new(StringIO.new("hi"))
|
|
197
|
+
form_file.read
|
|
198
|
+
|
|
199
|
+
assert_nil form_file.read(1)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def test_read_after_eof_returns_empty_string_without_length
|
|
203
|
+
form_file = HTTP::FormData::File.new(StringIO.new("hi"))
|
|
204
|
+
form_file.read
|
|
205
|
+
|
|
206
|
+
assert_equal "", form_file.read
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# --- File#close ---
|
|
210
|
+
|
|
211
|
+
def test_close_with_string_path_closes_io
|
|
212
|
+
form_file = HTTP::FormData::File.new(fixture_path.to_s)
|
|
213
|
+
form_file.read
|
|
214
|
+
form_file.close
|
|
215
|
+
|
|
216
|
+
assert_raises(IOError) { form_file.read }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def test_close_with_pathname_closes_io
|
|
220
|
+
form_file = HTTP::FormData::File.new(fixture_path)
|
|
221
|
+
form_file.read
|
|
222
|
+
form_file.close
|
|
223
|
+
|
|
224
|
+
assert_raises(IOError) { form_file.read }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def test_close_with_io_does_not_close
|
|
228
|
+
io = StringIO.new("hello")
|
|
229
|
+
HTTP::FormData::File.new(io).close
|
|
230
|
+
|
|
231
|
+
assert_equal "hello", io.read
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def test_close_is_idempotent
|
|
235
|
+
form_file = HTTP::FormData::File.new(fixture_path.to_s)
|
|
236
|
+
form_file.close
|
|
237
|
+
form_file.close
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def test_close_with_string_subclass_closes_io
|
|
241
|
+
form_file = HTTP::FormData::File.new(Class.new(String).new(fixture_path.to_s))
|
|
242
|
+
form_file.read
|
|
243
|
+
form_file.close
|
|
244
|
+
|
|
245
|
+
assert_raises(IOError) { form_file.read }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def test_close_with_pathname_subclass_closes_io
|
|
249
|
+
form_file = HTTP::FormData::File.new(Class.new(Pathname).new(fixture_path.to_s))
|
|
250
|
+
form_file.read
|
|
251
|
+
form_file.close
|
|
252
|
+
|
|
253
|
+
assert_raises(IOError) { form_file.read }
|
|
254
|
+
end
|
|
255
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
The HTTP Gem is an easy-to-use client library for making requests from Ruby.
|