wreq 1.2.4 → 1.2.6
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/Cargo.lock +22 -19
- data/Cargo.toml +15 -2
- data/crates/wreq-util/.github/FUNDING.yml +15 -0
- data/crates/wreq-util/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/crates/wreq-util/.github/ISSUE_TEMPLATE/custom.md +10 -0
- data/crates/wreq-util/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/crates/wreq-util/.github/dependabot.yml +22 -0
- data/crates/wreq-util/.github/workflows/ci.yml +82 -0
- data/crates/wreq-util/.github/workflows/release-plz.yml +52 -0
- data/crates/wreq-util/.gitignore +24 -0
- data/crates/wreq-util/CHANGELOG.md +74 -0
- data/crates/wreq-util/Cargo.toml +94 -0
- data/crates/wreq-util/LICENSE +201 -0
- data/crates/wreq-util/README.md +62 -0
- data/crates/wreq-util/examples/emulate.rs +39 -0
- data/crates/wreq-util/examples/tower_delay.rs +191 -0
- data/crates/wreq-util/release-plz.toml +2 -0
- data/crates/wreq-util/rustfmt.toml +5 -0
- data/crates/wreq-util/src/emulate/compress.rs +85 -0
- data/crates/wreq-util/src/emulate/macros.rs +372 -0
- data/crates/wreq-util/src/emulate/profile/chrome/header.rs +74 -0
- data/crates/wreq-util/src/emulate/profile/chrome/http2.rs +65 -0
- data/crates/wreq-util/src/emulate/profile/chrome/tls.rs +153 -0
- data/crates/wreq-util/src/emulate/profile/chrome.rs +2028 -0
- data/crates/wreq-util/src/emulate/profile/firefox/header.rs +37 -0
- data/crates/wreq-util/src/emulate/profile/firefox/http2.rs +113 -0
- data/crates/wreq-util/src/emulate/profile/firefox/tls.rs +253 -0
- data/crates/wreq-util/src/emulate/profile/firefox.rs +518 -0
- data/crates/wreq-util/src/emulate/profile/okhttp.rs +241 -0
- data/crates/wreq-util/src/emulate/profile/opera/header.rs +29 -0
- data/crates/wreq-util/src/emulate/profile/opera/http2.rs +50 -0
- data/crates/wreq-util/src/emulate/profile/opera/tls.rs +97 -0
- data/crates/wreq-util/src/emulate/profile/opera.rs +299 -0
- data/crates/wreq-util/src/emulate/profile/safari/header.rs +59 -0
- data/crates/wreq-util/src/emulate/profile/safari/http2.rs +116 -0
- data/crates/wreq-util/src/emulate/profile/safari/tls.rs +177 -0
- data/crates/wreq-util/src/emulate/profile/safari.rs +222 -0
- data/crates/wreq-util/src/emulate/profile.rs +47 -0
- data/crates/wreq-util/src/emulate.rs +369 -0
- data/crates/wreq-util/src/lib.rs +14 -0
- data/crates/wreq-util/src/rand.rs +24 -0
- data/crates/wreq-util/src/tower/delay/future.rs +43 -0
- data/crates/wreq-util/src/tower/delay/layer.rs +201 -0
- data/crates/wreq-util/src/tower/delay/service.rs +190 -0
- data/crates/wreq-util/src/tower/delay.rs +98 -0
- data/crates/wreq-util/src/tower.rs +7 -0
- data/crates/wreq-util/tests/client.rs +153 -0
- data/crates/wreq-util/tests/emulate_chrome.rs +270 -0
- data/crates/wreq-util/tests/emulate_firefox.rs +92 -0
- data/crates/wreq-util/tests/emulate_okhttp.rs +46 -0
- data/crates/wreq-util/tests/emulate_opera.rs +113 -0
- data/crates/wreq-util/tests/emulate_safari.rs +151 -0
- data/crates/wreq-util/tests/support/mod.rs +55 -0
- data/crates/wreq-util/tests/support/server.rs +232 -0
- data/examples/emulate_request.rb +1 -1
- data/lib/wreq.rb +72 -45
- data/lib/wreq_ruby/body.rb +30 -9
- data/lib/wreq_ruby/client.rb +88 -55
- data/lib/wreq_ruby/cookie.rb +81 -21
- data/lib/wreq_ruby/emulate.rb +77 -7
- data/lib/wreq_ruby/error.rb +5 -7
- data/lib/wreq_ruby/header.rb +205 -114
- data/lib/wreq_ruby/http.rb +74 -0
- data/lib/wreq_ruby/response.rb +31 -3
- data/script/build_windows_gnu.ps1 +6 -0
- data/src/arch.rs +22 -0
- data/src/client/body/form.rs +2 -0
- data/src/client/body/json.rs +47 -14
- data/src/client/body/stream.rs +147 -43
- data/src/client/body.rs +11 -15
- data/src/client/param.rs +7 -7
- data/src/client/req.rs +87 -47
- data/src/client/resp.rs +23 -24
- data/src/client.rs +310 -230
- data/src/cookie.rs +280 -87
- data/src/emulate.rs +86 -50
- data/src/error.rs +198 -45
- data/src/extractor.rs +16 -76
- data/src/header.rs +318 -130
- data/src/http.rs +62 -33
- data/src/lib.rs +26 -20
- data/src/macros.rs +71 -46
- data/src/options.rs +284 -0
- data/src/rt.rs +22 -11
- data/src/serde/de/array_deserializer.rs +48 -0
- data/src/serde/de/array_enumerator.rs +59 -0
- data/src/serde/de/deserializer.rs +307 -0
- data/src/serde/de/enum_deserializer.rs +47 -0
- data/src/serde/de/hash_deserializer.rs +89 -0
- data/src/serde/de/number_deserializer.rs +51 -0
- data/src/serde/de/variant_deserializer.rs +101 -0
- data/src/serde/de.rs +33 -0
- data/src/serde/error.rs +146 -0
- data/src/serde/ser/enums.rs +14 -0
- data/src/serde/ser/map_serializer.rs +56 -0
- data/src/serde/ser/seq_serializer.rs +71 -0
- data/src/serde/ser/serializer.rs +194 -0
- data/src/serde/ser/struct_serializer.rs +106 -0
- data/src/serde/ser/struct_variant_serializer.rs +44 -0
- data/src/serde/ser/tuple_variant_serializer.rs +41 -0
- data/src/serde/ser.rs +18 -0
- data/src/serde/tests.rs +237 -0
- data/src/serde.rs +202 -0
- data/test/body_sender_test.rb +246 -0
- data/test/cookie_test.rb +211 -10
- data/test/header_test.rb +228 -192
- data/test/json_precision_test.rb +209 -0
- data/test/option_validation_test.rb +311 -0
- data/test/stream_test.rb +36 -0
- data/test/value_semantics_test.rb +238 -0
- metadata +77 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
require "json"
|
|
3
|
+
require "socket"
|
|
4
|
+
|
|
5
|
+
class JsonPrecisionTest < Minitest::Test
|
|
6
|
+
INTEGER_BOUNDARIES = [
|
|
7
|
+
(2**53) - 1,
|
|
8
|
+
2**53,
|
|
9
|
+
(2**63) - 1,
|
|
10
|
+
2**63,
|
|
11
|
+
(2**64) - 1,
|
|
12
|
+
-((2**53) - 1),
|
|
13
|
+
-(2**53),
|
|
14
|
+
-((2**63) - 1),
|
|
15
|
+
-(2**63),
|
|
16
|
+
-(2**63) - 1,
|
|
17
|
+
-((2**64) - 1),
|
|
18
|
+
-(2**64),
|
|
19
|
+
2**100,
|
|
20
|
+
-(2**100),
|
|
21
|
+
2**256,
|
|
22
|
+
-(2**256)
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
def test_response_json_matches_json_parse_for_large_integers
|
|
26
|
+
payload = precision_payload
|
|
27
|
+
|
|
28
|
+
with_json_server(response_body: JSON.generate(payload)) do |url, _requests|
|
|
29
|
+
response = Wreq.get(url)
|
|
30
|
+
expected = JSON.parse(response.bytes)
|
|
31
|
+
actual = response.json
|
|
32
|
+
|
|
33
|
+
assert_equal payload, expected
|
|
34
|
+
assert_equal expected, actual
|
|
35
|
+
assert actual.fetch("integers").all? { |value| value.instance_of?(Integer) }
|
|
36
|
+
assert_instance_of Float, actual.fetch("fraction")
|
|
37
|
+
assert_instance_of Float, actual.fetch("exponent")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_request_json_preserves_large_integers_and_nested_values
|
|
42
|
+
payload = precision_payload
|
|
43
|
+
|
|
44
|
+
with_json_server do |url, requests|
|
|
45
|
+
response = Wreq.post(url, json: payload)
|
|
46
|
+
request = requests.pop
|
|
47
|
+
|
|
48
|
+
assert_equal "application/json", request.fetch(:headers).fetch("content-type")
|
|
49
|
+
assert_equal JSON.generate(payload), request.fetch(:body)
|
|
50
|
+
assert_equal payload, response.json
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_request_json_nil_is_serialized_as_null
|
|
55
|
+
with_json_server do |url, requests|
|
|
56
|
+
response = Wreq.post(url, json: nil)
|
|
57
|
+
request = requests.pop
|
|
58
|
+
|
|
59
|
+
assert_equal "null", request.fetch(:body)
|
|
60
|
+
assert_nil response.json
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_request_json_accepts_symbols_and_preserves_object_order
|
|
65
|
+
payload = {second: 2**100, first: :value}
|
|
66
|
+
|
|
67
|
+
with_json_server do |url, requests|
|
|
68
|
+
response = Wreq.post(url, json: payload)
|
|
69
|
+
request = requests.pop
|
|
70
|
+
actual = response.json
|
|
71
|
+
|
|
72
|
+
assert_equal JSON.generate(payload), request.fetch(:body)
|
|
73
|
+
assert_equal ["second", "first"], actual.keys
|
|
74
|
+
assert_equal({"second" => 2**100, "first" => "value"}, actual)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_request_json_enforces_documented_nesting_limit
|
|
79
|
+
accepted = 0
|
|
80
|
+
100.times { accepted = [accepted] }
|
|
81
|
+
|
|
82
|
+
with_json_server do |url, requests|
|
|
83
|
+
Wreq.post(url, json: accepted)
|
|
84
|
+
|
|
85
|
+
assert_equal JSON.generate(accepted), requests.pop.fetch(:body)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
error = assert_raises(Wreq::BuilderError) do
|
|
89
|
+
Wreq.post("http://127.0.0.1:1/", json: [accepted])
|
|
90
|
+
end
|
|
91
|
+
assert_match(/nesting exceeds 100 levels/, error.message)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_unsupported_request_json_raises_before_socket_io
|
|
95
|
+
server = TCPServer.new("127.0.0.1", 0)
|
|
96
|
+
port = server.addr[1]
|
|
97
|
+
|
|
98
|
+
error = assert_raises(Wreq::BuilderError) do
|
|
99
|
+
Wreq.post("http://127.0.0.1:#{port}/", json: {"value" => Float::NAN})
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
cyclic = []
|
|
103
|
+
cyclic << cyclic
|
|
104
|
+
nesting_error = assert_raises(Wreq::BuilderError) do
|
|
105
|
+
Wreq.post("http://127.0.0.1:#{port}/", json: cyclic)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
cyclic_hash = {}
|
|
109
|
+
cyclic_hash["self"] = cyclic_hash
|
|
110
|
+
hash_nesting_error = assert_raises(Wreq::BuilderError) do
|
|
111
|
+
Wreq.post("http://127.0.0.1:#{port}/", json: cyclic_hash)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
key_error = assert_raises(Wreq::BuilderError) do
|
|
115
|
+
Wreq.post("http://127.0.0.1:#{port}/", json: {1 => "value"})
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
assert_match(/non-finite/, error.message)
|
|
119
|
+
assert_match(/nesting/, nesting_error.message)
|
|
120
|
+
assert_match(/nesting/, hash_nesting_error.message)
|
|
121
|
+
assert_match(/keys/, key_error.message)
|
|
122
|
+
assert_equal :wait_readable, server.accept_nonblock(exception: false)
|
|
123
|
+
ensure
|
|
124
|
+
server&.close unless server&.closed?
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_invalid_response_json_raises_decoding_error
|
|
128
|
+
with_json_server(response_body: '{"id":') do |url, _requests|
|
|
129
|
+
response = Wreq.get(url)
|
|
130
|
+
|
|
131
|
+
assert_raises(Wreq::DecodingError) { response.json }
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_fractional_and_large_exponent_numbers_match_json_parse
|
|
136
|
+
source = '{"fraction":0.12345678901234567890123456789,"exponent":1e400}'
|
|
137
|
+
|
|
138
|
+
with_json_server(response_body: source) do |url, _requests|
|
|
139
|
+
response = Wreq.get(url)
|
|
140
|
+
expected = JSON.parse(response.bytes)
|
|
141
|
+
actual = response.json
|
|
142
|
+
|
|
143
|
+
assert_equal expected, actual
|
|
144
|
+
assert_instance_of Float, actual.fetch("fraction")
|
|
145
|
+
assert_equal Float::INFINITY, actual.fetch("exponent")
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def precision_payload
|
|
152
|
+
{
|
|
153
|
+
"integers" => INTEGER_BOUNDARIES,
|
|
154
|
+
"nested" => {
|
|
155
|
+
"array" => [2**100, {"negative" => -(2**100)}],
|
|
156
|
+
"object" => {"unsigned_64_max" => (2**64) - 1}
|
|
157
|
+
},
|
|
158
|
+
"shapes" => ["text", true, false, nil],
|
|
159
|
+
"fraction" => 1.25,
|
|
160
|
+
"exponent" => 1.0e40
|
|
161
|
+
}
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def with_json_server(response_body: nil)
|
|
165
|
+
server = TCPServer.new("127.0.0.1", 0)
|
|
166
|
+
port = server.addr[1]
|
|
167
|
+
requests = Queue.new
|
|
168
|
+
thread = Thread.new do
|
|
169
|
+
socket = server.accept
|
|
170
|
+
begin
|
|
171
|
+
request_line = socket.gets
|
|
172
|
+
headers = read_headers(socket)
|
|
173
|
+
content_length = headers.fetch("content-length", "0").to_i
|
|
174
|
+
body = content_length.zero? ? "" : socket.read(content_length)
|
|
175
|
+
requests << {request_line: request_line, headers: headers, body: body}
|
|
176
|
+
|
|
177
|
+
response = response_body.nil? ? body : response_body
|
|
178
|
+
socket.write "HTTP/1.1 200 OK\r\n"
|
|
179
|
+
socket.write "Content-Type: application/json\r\n"
|
|
180
|
+
socket.write "Content-Length: #{response.bytesize}\r\n"
|
|
181
|
+
socket.write "Connection: close\r\n\r\n"
|
|
182
|
+
socket.write response
|
|
183
|
+
ensure
|
|
184
|
+
socket.close unless socket.closed?
|
|
185
|
+
end
|
|
186
|
+
rescue IOError, SystemCallError
|
|
187
|
+
nil
|
|
188
|
+
ensure
|
|
189
|
+
server.close unless server.closed?
|
|
190
|
+
end
|
|
191
|
+
thread.report_on_exception = false
|
|
192
|
+
|
|
193
|
+
yield "http://127.0.0.1:#{port}/", requests
|
|
194
|
+
ensure
|
|
195
|
+
server&.close unless server&.closed?
|
|
196
|
+
thread&.join(5)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def read_headers(socket)
|
|
200
|
+
headers = {}
|
|
201
|
+
while (line = socket.gets)
|
|
202
|
+
break if line == "\r\n"
|
|
203
|
+
|
|
204
|
+
name, value = line.split(":", 2)
|
|
205
|
+
headers[name.downcase] = value.strip
|
|
206
|
+
end
|
|
207
|
+
headers
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class OptionValidationTest < Minitest::Test
|
|
4
|
+
INVALID_URL = "not a url"
|
|
5
|
+
|
|
6
|
+
def test_unknown_client_option_names_every_invalid_key_without_values
|
|
7
|
+
secret = "must-not-appear"
|
|
8
|
+
|
|
9
|
+
error = assert_raises(ArgumentError) do
|
|
10
|
+
Wreq::Client.new(timout: secret, history: secret)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
assert_includes error.message, ":timout"
|
|
14
|
+
assert_includes error.message, ":history"
|
|
15
|
+
refute_includes error.message, secret
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_unknown_options_are_checked_before_known_values
|
|
19
|
+
secret = "must-not-appear"
|
|
20
|
+
|
|
21
|
+
error = assert_raises(ArgumentError) do
|
|
22
|
+
Wreq::Client.new(timeout: secret, history: Object.new, timout: secret)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
assert_includes error.message, ":history"
|
|
26
|
+
assert_includes error.message, ":timout"
|
|
27
|
+
refute_includes error.message, secret
|
|
28
|
+
|
|
29
|
+
conflict_error = assert_raises(ArgumentError) do
|
|
30
|
+
Wreq::Client.new(http1_only: true, http2_only: true, history: secret)
|
|
31
|
+
end
|
|
32
|
+
assert_includes conflict_error.message, ":history"
|
|
33
|
+
refute_includes conflict_error.message, secret
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_unknown_request_options_raise_for_client_and_module_methods
|
|
37
|
+
client_error = assert_raises(ArgumentError) do
|
|
38
|
+
Wreq::Client.new.get(INVALID_URL, history: true)
|
|
39
|
+
end
|
|
40
|
+
module_error = assert_raises(ArgumentError) do
|
|
41
|
+
Wreq.get(INVALID_URL, history: true)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
assert_includes client_error.message, ":history"
|
|
45
|
+
assert_includes module_error.message, ":history"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_hash_expansion_uses_the_same_validation
|
|
49
|
+
valid = {timeout: 1, allow_redirects: true, max_redirects: 2}
|
|
50
|
+
invalid = {timout: 1}
|
|
51
|
+
|
|
52
|
+
assert_instance_of Wreq::Client, Wreq::Client.new(**valid)
|
|
53
|
+
error = assert_raises(ArgumentError) { Wreq::Client.new(**invalid) }
|
|
54
|
+
assert_includes error.message, ":timout"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_string_option_keys_are_supported_and_duplicate_forms_are_rejected
|
|
58
|
+
assert_instance_of Wreq::Client, Wreq::Client.new({"timeout" => 1})
|
|
59
|
+
|
|
60
|
+
error = assert_raises(ArgumentError) do
|
|
61
|
+
Wreq::Client.new({:timeout => 1, "timeout" => 2})
|
|
62
|
+
end
|
|
63
|
+
assert_includes error.message, "duplicate option: :timeout"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_non_string_or_symbol_option_key_raises_type_error
|
|
67
|
+
error = assert_raises(TypeError) { Wreq::Client.new({1 => true}) }
|
|
68
|
+
|
|
69
|
+
assert_includes error.message, "option keys"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_hash_subclass_cannot_hide_unknown_options
|
|
73
|
+
options_class = Class.new(Hash) do
|
|
74
|
+
def keys
|
|
75
|
+
[]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
options = options_class.new
|
|
79
|
+
options[:timout] = 1
|
|
80
|
+
|
|
81
|
+
error = assert_raises(ArgumentError) { Wreq::Client.new(options) }
|
|
82
|
+
assert_includes error.message, ":timout"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_invalid_known_values_name_the_option
|
|
86
|
+
timeout_error = assert_raises(TypeError) do
|
|
87
|
+
Wreq::Client.new(timeout: "slow")
|
|
88
|
+
end
|
|
89
|
+
address_error = assert_raises(ArgumentError) do
|
|
90
|
+
Wreq::Client.new(local_address: "not-an-address")
|
|
91
|
+
end
|
|
92
|
+
proxy_error = assert_raises(TypeError) do
|
|
93
|
+
Wreq::Client.new(proxy: Object.new)
|
|
94
|
+
end
|
|
95
|
+
proxy_uri_error = assert_raises(Wreq::BuilderError) do
|
|
96
|
+
Wreq::Client.new(proxy: "invalid://")
|
|
97
|
+
end
|
|
98
|
+
orig_headers_error = assert_raises(Wreq::BuilderError) do
|
|
99
|
+
Wreq.get(INVALID_URL, orig_headers: ["Accept", Object.new])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
assert_includes timeout_error.message, ":timeout"
|
|
103
|
+
assert_includes address_error.message, ":local_address"
|
|
104
|
+
assert_includes proxy_error.message, ":proxy"
|
|
105
|
+
assert_includes proxy_uri_error.message, ":proxy"
|
|
106
|
+
assert_includes orig_headers_error.message, ":orig_headers"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_consumed_body_sender_error_preserves_class_and_names_option
|
|
110
|
+
sender = Wreq::BodySender.new(1)
|
|
111
|
+
|
|
112
|
+
assert_raises(Wreq::BuilderError) do
|
|
113
|
+
Wreq.post(INVALID_URL, body: sender)
|
|
114
|
+
end
|
|
115
|
+
error = assert_raises(Wreq::MemoryError) do
|
|
116
|
+
Wreq.post(INVALID_URL, body: sender)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
assert_includes error.message, ":body"
|
|
120
|
+
ensure
|
|
121
|
+
sender&.close
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_option_conversion_preserves_raised_exception_class
|
|
125
|
+
source = Object.new
|
|
126
|
+
source.define_singleton_method(:to_a) { raise IOError, "boom" }
|
|
127
|
+
|
|
128
|
+
error = assert_raises(IOError) do
|
|
129
|
+
Wreq::Client.new(headers: source)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
assert_includes error.message, ":headers"
|
|
133
|
+
assert_includes error.message, "boom"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_out_of_range_integer_names_the_option
|
|
137
|
+
error = assert_raises(ArgumentError) do
|
|
138
|
+
Wreq::Client.new(timeout: 2**256)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
assert_includes error.message, ":timeout"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_invalid_request_value_names_the_option_before_network_io
|
|
145
|
+
error = assert_raises(TypeError) do
|
|
146
|
+
Wreq.get(INVALID_URL, cookies: Object.new)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
assert_includes error.message, ":cookies"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_client_constructor_rejects_non_hash_and_extra_arguments
|
|
153
|
+
assert_raises(TypeError) { Wreq::Client.new("ignored") }
|
|
154
|
+
assert_raises(ArgumentError) { Wreq::Client.new({}, {}) }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_emulation_constructor_rejects_invalid_arguments_and_options
|
|
158
|
+
assert_raises(TypeError) { Wreq::Emulation.new("ignored") }
|
|
159
|
+
assert_raises(ArgumentError) { Wreq::Emulation.new({}, {}) }
|
|
160
|
+
|
|
161
|
+
error = assert_raises(ArgumentError) { Wreq::Emulation.new(unknown: true) }
|
|
162
|
+
assert_includes error.message, ":unknown"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def test_invalid_emulation_values_name_the_option
|
|
166
|
+
http2_error = assert_raises(TypeError) do
|
|
167
|
+
Wreq::Emulation.new(http2: "yes")
|
|
168
|
+
end
|
|
169
|
+
profile_error = assert_raises(TypeError) do
|
|
170
|
+
Wreq::Emulation.new(profile: Object.new)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
assert_includes http2_error.message, ":http2"
|
|
174
|
+
assert_includes profile_error.message, ":profile"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def test_body_options_are_mutually_exclusive
|
|
178
|
+
error = assert_raises(ArgumentError) do
|
|
179
|
+
Wreq.post(INVALID_URL, body: "raw", form: {a: 1}, json: {b: 2})
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
assert_includes error.message, ":body"
|
|
183
|
+
assert_includes error.message, ":form"
|
|
184
|
+
assert_includes error.message, ":json"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def test_body_conflict_does_not_consume_sender
|
|
188
|
+
sender = Wreq::BodySender.new(1)
|
|
189
|
+
|
|
190
|
+
assert_raises(ArgumentError) do
|
|
191
|
+
Wreq.post(INVALID_URL, body: sender, json: {value: true})
|
|
192
|
+
end
|
|
193
|
+
assert_raises(Wreq::BuilderError) do
|
|
194
|
+
Wreq.post(INVALID_URL, body: sender)
|
|
195
|
+
end
|
|
196
|
+
ensure
|
|
197
|
+
sender&.close
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def test_dependent_option_error_does_not_consume_sender
|
|
201
|
+
sender = Wreq::BodySender.new(1)
|
|
202
|
+
|
|
203
|
+
assert_raises(ArgumentError) do
|
|
204
|
+
Wreq.post(INVALID_URL, body: sender, max_redirects: 2)
|
|
205
|
+
end
|
|
206
|
+
assert_raises(Wreq::BuilderError) do
|
|
207
|
+
Wreq.post(INVALID_URL, body: sender)
|
|
208
|
+
end
|
|
209
|
+
ensure
|
|
210
|
+
sender&.close
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_authentication_options_are_mutually_exclusive
|
|
214
|
+
error = assert_raises(ArgumentError) do
|
|
215
|
+
Wreq.get(
|
|
216
|
+
INVALID_URL,
|
|
217
|
+
auth: "secret",
|
|
218
|
+
bearer_auth: "secret",
|
|
219
|
+
basic_auth: ["user", "secret"]
|
|
220
|
+
)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
assert_includes error.message, ":auth"
|
|
224
|
+
assert_includes error.message, ":bearer_auth"
|
|
225
|
+
assert_includes error.message, ":basic_auth"
|
|
226
|
+
refute_includes error.message, "secret"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def test_protocol_only_options_are_mutually_exclusive
|
|
230
|
+
error = assert_raises(ArgumentError) do
|
|
231
|
+
Wreq::Client.new(http1_only: true, http2_only: true)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
assert_includes error.message, ":http1_only"
|
|
235
|
+
assert_includes error.message, ":http2_only"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def test_validation_reports_the_first_failed_rule
|
|
239
|
+
error = assert_raises(ArgumentError) do
|
|
240
|
+
Wreq::Client.new(
|
|
241
|
+
http1_only: true,
|
|
242
|
+
http2_only: true,
|
|
243
|
+
max_redirects: 2,
|
|
244
|
+
headers: Object.new
|
|
245
|
+
)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
assert_includes error.message, ":http1_only"
|
|
249
|
+
assert_includes error.message, ":http2_only"
|
|
250
|
+
refute_includes error.message, ":max_redirects"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def test_explicit_proxy_and_no_proxy_are_mutually_exclusive
|
|
254
|
+
error = assert_raises(ArgumentError) do
|
|
255
|
+
Wreq::Client.new(proxy: "http://127.0.0.1:8080", no_proxy: true)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
assert_includes error.message, ":proxy"
|
|
259
|
+
assert_includes error.message, ":no_proxy"
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def test_max_redirects_requires_redirects_to_be_enabled
|
|
263
|
+
client_error = assert_raises(ArgumentError) do
|
|
264
|
+
Wreq::Client.new(max_redirects: 2)
|
|
265
|
+
end
|
|
266
|
+
disabled_error = assert_raises(ArgumentError) do
|
|
267
|
+
Wreq::Client.new(allow_redirects: false, max_redirects: 2)
|
|
268
|
+
end
|
|
269
|
+
request_error = assert_raises(ArgumentError) do
|
|
270
|
+
Wreq.get(INVALID_URL, max_redirects: 2)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
[client_error, disabled_error, request_error].each do |error|
|
|
274
|
+
assert_includes error.message, ":max_redirects"
|
|
275
|
+
assert_includes error.message, ":allow_redirects"
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def test_unsupported_platform_option_raises_argument_error
|
|
280
|
+
return if RUBY_PLATFORM.match?(/linux|android|fuchsia/)
|
|
281
|
+
|
|
282
|
+
error = assert_raises(ArgumentError) do
|
|
283
|
+
Wreq::Client.new(tcp_user_timeout: 1)
|
|
284
|
+
end
|
|
285
|
+
assert_instance_of ArgumentError, error
|
|
286
|
+
assert_includes error.message, ":tcp_user_timeout"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def test_windows_rejects_interface_for_client_and_request
|
|
290
|
+
return unless Gem.win_platform?
|
|
291
|
+
|
|
292
|
+
client_error = assert_raises(ArgumentError) do
|
|
293
|
+
Wreq::Client.new(interface: "Ethernet")
|
|
294
|
+
end
|
|
295
|
+
request_error = assert_raises(ArgumentError) do
|
|
296
|
+
Wreq.get(INVALID_URL, interface: "Ethernet")
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
assert_instance_of ArgumentError, client_error
|
|
300
|
+
assert_instance_of ArgumentError, request_error
|
|
301
|
+
assert_includes client_error.message, ":interface"
|
|
302
|
+
assert_includes request_error.message, ":interface"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def test_valid_nil_and_zero_option_construction
|
|
306
|
+
assert_instance_of Wreq::Client, Wreq::Client.new
|
|
307
|
+
assert_instance_of Wreq::Client,
|
|
308
|
+
Wreq::Client.new(proxy: nil, interface: nil, tcp_user_timeout: nil)
|
|
309
|
+
assert_instance_of Wreq::Emulation, Wreq::Emulation.new(profile: nil, http2: true)
|
|
310
|
+
end
|
|
311
|
+
end
|
data/test/stream_test.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "test_helper"
|
|
2
2
|
require "socket"
|
|
3
|
+
require "timeout"
|
|
3
4
|
|
|
4
5
|
class StreamTest < Minitest::Test
|
|
5
6
|
def test_simple_push_stream
|
|
@@ -174,6 +175,41 @@ class StreamTest < Minitest::Test
|
|
|
174
175
|
assert killed, "Connect+timeout phase should be interruptible"
|
|
175
176
|
end
|
|
176
177
|
|
|
178
|
+
def test_thread_raise_propagates_ruby_interrupt
|
|
179
|
+
server = TCPServer.new("127.0.0.1", 0)
|
|
180
|
+
accepted = Queue.new
|
|
181
|
+
errors = Queue.new
|
|
182
|
+
|
|
183
|
+
server_thread = Thread.new do
|
|
184
|
+
socket = server.accept
|
|
185
|
+
accepted << true
|
|
186
|
+
sleep
|
|
187
|
+
ensure
|
|
188
|
+
socket&.close
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
request_thread = Thread.new do
|
|
192
|
+
Wreq.get("http://127.0.0.1:#{server.addr[1]}/", timeout: 60)
|
|
193
|
+
rescue Interrupt, StandardError => error
|
|
194
|
+
errors << error
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
Timeout.timeout(5) { accepted.pop }
|
|
198
|
+
request_thread.raise(Interrupt, "test interrupt")
|
|
199
|
+
|
|
200
|
+
assert request_thread.join(5), "Interrupted request thread should stop"
|
|
201
|
+
|
|
202
|
+
error = Timeout.timeout(1) { errors.pop }
|
|
203
|
+
assert_instance_of Interrupt, error
|
|
204
|
+
assert_equal "test interrupt", error.message
|
|
205
|
+
ensure
|
|
206
|
+
request_thread&.kill
|
|
207
|
+
request_thread&.join(1)
|
|
208
|
+
server_thread&.kill
|
|
209
|
+
server_thread&.join(1)
|
|
210
|
+
server&.close
|
|
211
|
+
end
|
|
212
|
+
|
|
177
213
|
def test_thread_interrupt_body_reading
|
|
178
214
|
url = "#{HTTPBIN_URL}/drip?duration=5&numbytes=5"
|
|
179
215
|
thread = Thread.new do
|