wreq 1.1.0 → 1.2.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/Cargo.lock +216 -142
- data/Cargo.toml +4 -4
- data/README.md +5 -5
- data/examples/cookie.rb +24 -0
- data/examples/{emulation_request.rb → emulate_request.rb} +8 -8
- data/lib/wreq.rb +45 -55
- data/lib/wreq_ruby/client.rb +71 -63
- data/lib/wreq_ruby/cookie.rb +21 -9
- data/lib/wreq_ruby/{emulation.rb → emulate.rb} +57 -29
- data/lib/wreq_ruby/header.rb +8 -0
- data/lib/wreq_ruby/http.rb +14 -0
- data/lib/wreq_ruby/response.rb +22 -21
- data/src/client/body/stream.rs +9 -14
- data/src/client/req.rs +34 -40
- data/src/client/resp.rs +46 -40
- data/src/client.rs +34 -30
- data/src/cookie.rs +46 -11
- data/src/emulate.rs +183 -168
- data/src/error.rs +8 -0
- data/src/extractor.rs +2 -38
- data/src/header.rs +37 -6
- data/src/macros.rs +5 -0
- data/src/rt.rs +0 -18
- data/test/client_cookie_test.rb +1 -1
- data/test/cookie_test.rb +30 -16
- data/test/emulation_test.rb +8 -8
- data/test/error_handling_test.rb +4 -1
- data/test/inspect_test.rb +125 -0
- data/test/stream_test.rb +292 -2
- metadata +5 -3
data/test/cookie_test.rb
CHANGED
|
@@ -19,9 +19,9 @@ class CookieTest < Minitest::Test
|
|
|
19
19
|
assert_equal 0, cookies.length
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def
|
|
22
|
+
def test_add_and_get_all
|
|
23
23
|
set_cookie = "sid=abc123; Path=/; Domain=example.com; HttpOnly; Secure"
|
|
24
|
-
@jar.
|
|
24
|
+
@jar.add(set_cookie, @base_url)
|
|
25
25
|
|
|
26
26
|
cookies = @jar.get_all
|
|
27
27
|
assert_kind_of Array, cookies
|
|
@@ -42,9 +42,9 @@ class CookieTest < Minitest::Test
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def test_add_multiple_and_remove
|
|
45
|
-
@jar.
|
|
46
|
-
@jar.
|
|
47
|
-
@jar.
|
|
45
|
+
@jar.add("a=1; Path=/", @base_url)
|
|
46
|
+
@jar.add("b=2; Path=/", @base_url)
|
|
47
|
+
@jar.add("c=3; Path=/", @base_url)
|
|
48
48
|
|
|
49
49
|
cookies = @jar.get_all
|
|
50
50
|
assert_equal 3, cookies.length
|
|
@@ -58,8 +58,8 @@ class CookieTest < Minitest::Test
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def test_clear
|
|
61
|
-
@jar.
|
|
62
|
-
@jar.
|
|
61
|
+
@jar.add("x=1; Path=/", @base_url)
|
|
62
|
+
@jar.add("y=2; Path=/", @base_url)
|
|
63
63
|
refute_empty @jar.get_all
|
|
64
64
|
|
|
65
65
|
@jar.clear
|
|
@@ -69,7 +69,7 @@ class CookieTest < Minitest::Test
|
|
|
69
69
|
def test_max_age_and_expires_optional
|
|
70
70
|
# Max-Age only
|
|
71
71
|
@jar.clear
|
|
72
|
-
@jar.
|
|
72
|
+
@jar.add("ma=1; Max-Age=3600; Path=/", @base_url)
|
|
73
73
|
c1 = @jar.get_all.find { |c| c.name == "ma" }
|
|
74
74
|
assert c1
|
|
75
75
|
# can be nil or Integer; just ensure responds and is truthy integer
|
|
@@ -81,7 +81,7 @@ class CookieTest < Minitest::Test
|
|
|
81
81
|
# Expires only
|
|
82
82
|
@jar.clear
|
|
83
83
|
t = Time.now + 3600
|
|
84
|
-
@jar.
|
|
84
|
+
@jar.add("exp=1; Expires=#{t.gmtime.strftime("%a, %d %b %Y %H:%M:%S GMT")}; Path=/", @base_url)
|
|
85
85
|
c2 = @jar.get_all.find { |c| c.name == "exp" }
|
|
86
86
|
assert c2
|
|
87
87
|
# expires returns Float (unix seconds) or nil
|
|
@@ -144,8 +144,8 @@ class CookieTest < Minitest::Test
|
|
|
144
144
|
|
|
145
145
|
def test_same_site_flags_from_parsed_header
|
|
146
146
|
@jar.clear
|
|
147
|
-
@jar.
|
|
148
|
-
@jar.
|
|
147
|
+
@jar.add("s1=1; Path=/; SameSite=Strict", @base_url)
|
|
148
|
+
@jar.add("s2=1; Path=/; SameSite=Lax", @base_url)
|
|
149
149
|
|
|
150
150
|
cookies = @jar.get_all
|
|
151
151
|
h = cookies.to_h { |ck| [ck.name, [ck.same_site_strict?, ck.same_site_lax?]] }
|
|
@@ -154,13 +154,27 @@ class CookieTest < Minitest::Test
|
|
|
154
154
|
assert_equal [false, true], h["s2"]
|
|
155
155
|
end
|
|
156
156
|
|
|
157
|
-
def
|
|
158
|
-
raw_value = "hello world?"
|
|
157
|
+
def test_request_uncompressed_cookies
|
|
159
158
|
client = Wreq::Client.new
|
|
160
159
|
resp = client.get(
|
|
161
|
-
"
|
|
162
|
-
cookies: {"
|
|
160
|
+
"https://httpbin.io/cookies",
|
|
161
|
+
cookies: {"foo" => "bar", "baz" => "qux"}
|
|
163
162
|
)
|
|
164
|
-
|
|
163
|
+
json = resp.json
|
|
164
|
+
assert_instance_of Hash, json
|
|
165
|
+
assert_equal "bar", json["foo"]
|
|
166
|
+
assert_equal "qux", json["baz"]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def test_request_compressed_cookies
|
|
170
|
+
client = Wreq::Client.new
|
|
171
|
+
resp = client.get(
|
|
172
|
+
"https://httpbin.io/cookies",
|
|
173
|
+
cookies: "foo=bar; baz=qux"
|
|
174
|
+
)
|
|
175
|
+
json = resp.json
|
|
176
|
+
assert_instance_of Hash, json
|
|
177
|
+
assert_equal "bar", json["foo"]
|
|
178
|
+
assert_equal "qux", json["baz"]
|
|
165
179
|
end
|
|
166
180
|
end
|
data/test/emulation_test.rb
CHANGED
|
@@ -4,18 +4,18 @@ require "test_helper"
|
|
|
4
4
|
|
|
5
5
|
class EmulationTest < Minitest::Test
|
|
6
6
|
def test_all_emulation_device_constants_are_non_nil
|
|
7
|
-
Wreq::
|
|
8
|
-
const = Wreq::
|
|
9
|
-
assert_instance_of Wreq::
|
|
10
|
-
"#{name} should be
|
|
7
|
+
Wreq::Profile.constants.each do |name|
|
|
8
|
+
const = Wreq::Profile.const_get(name)
|
|
9
|
+
assert_instance_of Wreq::Profile, const,
|
|
10
|
+
"#{name} should be Profile, got #{const.inspect}"
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def test_all_emulation_os_constants_are_non_nil
|
|
15
|
-
Wreq::
|
|
16
|
-
const = Wreq::
|
|
17
|
-
assert_instance_of Wreq::
|
|
18
|
-
"#{name} should be
|
|
15
|
+
Wreq::Platform.constants.each do |name|
|
|
16
|
+
const = Wreq::Platform.const_get(name)
|
|
17
|
+
assert_instance_of Wreq::Platform, const,
|
|
18
|
+
"#{name} should be Platform, got #{const.inspect}"
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
data/test/error_handling_test.rb
CHANGED
|
@@ -82,7 +82,10 @@ class ErrorHandlingTest < Minitest::Test
|
|
|
82
82
|
Wreq.get(url, proxy: proxy, timeout: 5)
|
|
83
83
|
flunk "Expected proxy connection error but got response"
|
|
84
84
|
rescue => e
|
|
85
|
-
|
|
85
|
+
assert(
|
|
86
|
+
e.is_a?(Wreq::ProxyConnectionError) || e.is_a?(Wreq::RequestError),
|
|
87
|
+
"Expected ProxyConnectionError or RequestError, got #{e.class}: #{e.message}"
|
|
88
|
+
)
|
|
86
89
|
end
|
|
87
90
|
end
|
|
88
91
|
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class InspectTest < Minitest::Test
|
|
6
|
+
# ---- Headers ----
|
|
7
|
+
|
|
8
|
+
def test_headers_inspect_empty
|
|
9
|
+
headers = Wreq::Headers.new
|
|
10
|
+
assert_equal "#<Wreq::Headers [0 headers]>", headers.inspect
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_headers_inspect_with_entries
|
|
14
|
+
headers = Wreq::Headers.new
|
|
15
|
+
headers.set("Content-Type", "text/html")
|
|
16
|
+
headers.set("Accept", "application/json")
|
|
17
|
+
assert_equal "#<Wreq::Headers [2 headers]>", headers.inspect
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# ---- Cookie ----
|
|
21
|
+
|
|
22
|
+
def test_cookie_inspect_minimal
|
|
23
|
+
c = Wreq::Cookie.new("sid", "secret123")
|
|
24
|
+
result = c.inspect
|
|
25
|
+
assert_includes result, "#<Wreq::Cookie"
|
|
26
|
+
assert_includes result, "sid"
|
|
27
|
+
refute_includes result, "secret123"
|
|
28
|
+
assert result.end_with?(">")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_cookie_inspect_with_domain_and_path
|
|
32
|
+
c = Wreq::Cookie.new("sid", "val",
|
|
33
|
+
domain: "example.com",
|
|
34
|
+
path: "/app")
|
|
35
|
+
result = c.inspect
|
|
36
|
+
assert_includes result, "domain=example.com"
|
|
37
|
+
assert_includes result, "path=/app"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_cookie_inspect_with_flags
|
|
41
|
+
c = Wreq::Cookie.new("sid", "val",
|
|
42
|
+
secure: true,
|
|
43
|
+
http_only: true)
|
|
44
|
+
result = c.inspect
|
|
45
|
+
assert_includes result, "secure"
|
|
46
|
+
assert_includes result, "http_only"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_cookie_inspect_omits_nil_attributes
|
|
50
|
+
c = Wreq::Cookie.new("sid", "val")
|
|
51
|
+
result = c.inspect
|
|
52
|
+
refute_includes result, "domain="
|
|
53
|
+
refute_includes result, "path="
|
|
54
|
+
refute_includes result, "secure"
|
|
55
|
+
refute_includes result, "http_only"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# ---- Jar ----
|
|
59
|
+
|
|
60
|
+
def test_jar_inspect_empty
|
|
61
|
+
jar = Wreq::Jar.new
|
|
62
|
+
assert_equal "#<Wreq::Jar [0 cookies]>", jar.inspect
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_jar_inspect_with_cookies
|
|
66
|
+
jar = Wreq::Jar.new
|
|
67
|
+
jar.add("a=1; Path=/", "https://example.com")
|
|
68
|
+
jar.add("b=2; Path=/", "https://example.com")
|
|
69
|
+
assert_equal "#<Wreq::Jar [2 cookies]>", jar.inspect
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# ---- Client ----
|
|
73
|
+
|
|
74
|
+
def test_client_inspect
|
|
75
|
+
client = Wreq::Client.new
|
|
76
|
+
assert_equal "#<Wreq::Client>", client.inspect
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_client_inspect_with_options
|
|
80
|
+
client = Wreq::Client.new(timeout: 30, gzip: true)
|
|
81
|
+
assert_equal "#<Wreq::Client>", client.inspect
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# ---- Response ----
|
|
85
|
+
|
|
86
|
+
def test_response_to_s_returns_body
|
|
87
|
+
response = Wreq.get("http://localhost:8080/json")
|
|
88
|
+
assert_equal response.text, response.to_s
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_response_inspect_format
|
|
92
|
+
response = Wreq.get("http://localhost:8080/json")
|
|
93
|
+
result = response.inspect
|
|
94
|
+
assert result.start_with?("#<Wreq::Response")
|
|
95
|
+
assert_includes result, "200"
|
|
96
|
+
assert result.end_with?(">")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# ---- StatusCode ----
|
|
100
|
+
|
|
101
|
+
def test_status_code_inspect
|
|
102
|
+
response = Wreq.get("http://localhost:8080/status/200")
|
|
103
|
+
result = response.status.inspect
|
|
104
|
+
assert result.start_with?("#<Wreq::StatusCode")
|
|
105
|
+
assert_includes result, response.status.to_s
|
|
106
|
+
assert result.end_with?(">")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# ---- Version ----
|
|
110
|
+
|
|
111
|
+
def test_version_inspect_from_constant
|
|
112
|
+
v = Wreq::Version::HTTP_11
|
|
113
|
+
result = v.inspect
|
|
114
|
+
assert result.start_with?("#<Wreq::Version")
|
|
115
|
+
assert_includes result, v.to_s
|
|
116
|
+
assert result.end_with?(">")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_version_inspect_from_response
|
|
120
|
+
response = Wreq.get("http://localhost:8080/get")
|
|
121
|
+
result = response.version.inspect
|
|
122
|
+
assert result.start_with?("#<Wreq::Version")
|
|
123
|
+
assert result.end_with?(">")
|
|
124
|
+
end
|
|
125
|
+
end
|
data/test/stream_test.rb
CHANGED
|
@@ -8,12 +8,16 @@ class StreamTest < Minitest::Test
|
|
|
8
8
|
3.times { |i| sender.push("chunk-#{i}\n") }
|
|
9
9
|
sender.close
|
|
10
10
|
end
|
|
11
|
+
|
|
11
12
|
resp = client.post("http://localhost:8080/post", body: sender, headers: {"Content-Type" => "text/plain"})
|
|
13
|
+
|
|
12
14
|
assert_equal 200, resp.code
|
|
15
|
+
|
|
13
16
|
echoed = resp.json["data"]
|
|
14
17
|
assert_includes echoed, "chunk-0"
|
|
15
18
|
assert_includes echoed, "chunk-1"
|
|
16
19
|
assert_includes echoed, "chunk-2"
|
|
20
|
+
|
|
17
21
|
producer.join
|
|
18
22
|
end
|
|
19
23
|
|
|
@@ -21,12 +25,124 @@ class StreamTest < Minitest::Test
|
|
|
21
25
|
client = Wreq::Client.new
|
|
22
26
|
resp = client.get("http://localhost:8080/stream/5")
|
|
23
27
|
chunks = []
|
|
28
|
+
|
|
24
29
|
resp.chunks do |chunk|
|
|
25
30
|
chunks << chunk
|
|
26
|
-
assert_kind_of String, chunk
|
|
31
|
+
assert_kind_of String, chunk, "Each yielded chunk must be a String"
|
|
27
32
|
assert_match(/\{.*\}/, chunk)
|
|
28
33
|
end
|
|
29
|
-
|
|
34
|
+
|
|
35
|
+
assert_equal 5, chunks.size, "Should yield exactly 5 chunks from /stream/5"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_chunks_yields_binary_encoding
|
|
39
|
+
client = Wreq::Client.new
|
|
40
|
+
resp = client.get("http://localhost:8080/stream/3")
|
|
41
|
+
|
|
42
|
+
resp.chunks do |chunk|
|
|
43
|
+
assert chunk.encoding == Encoding::BINARY || chunk.encoding == Encoding::ASCII_8BIT,
|
|
44
|
+
"Chunk should have binary encoding, got #{chunk.encoding}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_chunks_with_single_chunk_body
|
|
49
|
+
client = Wreq::Client.new
|
|
50
|
+
resp = client.get("http://localhost:8080/bytes/1024")
|
|
51
|
+
chunk_count = 0
|
|
52
|
+
total_bytes = 0
|
|
53
|
+
|
|
54
|
+
resp.chunks do |chunk|
|
|
55
|
+
chunk_count += 1
|
|
56
|
+
total_bytes += chunk.bytesize
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
assert chunk_count >= 1, "Should yield at least one chunk"
|
|
60
|
+
assert_equal 1024, total_bytes, "Total bytes should match content length"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_chunks_returns_nil
|
|
64
|
+
client = Wreq::Client.new
|
|
65
|
+
resp = client.get("http://localhost:8080/stream/3")
|
|
66
|
+
|
|
67
|
+
result = resp.chunks { |_chunk| :processing }
|
|
68
|
+
|
|
69
|
+
assert_nil result, "chunks should return nil after completion"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_chunks_with_empty_body
|
|
73
|
+
client = Wreq::Client.new
|
|
74
|
+
resp = client.get("http://localhost:8080/status/204")
|
|
75
|
+
chunk_count = 0
|
|
76
|
+
|
|
77
|
+
resp.chunks do |_chunk|
|
|
78
|
+
chunk_count += 1
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
assert_equal 0, chunk_count, "No chunks should be yielded for empty 204 response"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_chunks_without_block_raises_error
|
|
85
|
+
client = Wreq::Client.new
|
|
86
|
+
resp = client.get("http://localhost:8080/stream/3")
|
|
87
|
+
|
|
88
|
+
assert_raises(LocalJumpError) do
|
|
89
|
+
resp.chunks
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_other_threads_run_during_streaming
|
|
94
|
+
client = Wreq::Client.new
|
|
95
|
+
resp = client.get("http://localhost:8080/drip?duration=3&numbytes=3&delay=1")
|
|
96
|
+
|
|
97
|
+
counter = 0
|
|
98
|
+
tick_thread = Thread.new do
|
|
99
|
+
30.times do
|
|
100
|
+
counter += 1
|
|
101
|
+
sleep 0.1
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
chunks_received = 0
|
|
106
|
+
resp.chunks do |_chunk|
|
|
107
|
+
chunks_received += 1
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
tick_thread.join(10)
|
|
111
|
+
|
|
112
|
+
assert counter > 5,
|
|
113
|
+
"Counter only reached #{counter} - other threads may not be running during streaming. " \
|
|
114
|
+
"GVL should be released during I/O waits."
|
|
115
|
+
assert chunks_received >= 1, "Should have received at least one chunk"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_multiple_concurrent_streams
|
|
119
|
+
client = Wreq::Client.new
|
|
120
|
+
results = {}
|
|
121
|
+
done = {}
|
|
122
|
+
|
|
123
|
+
t1 = Thread.new do
|
|
124
|
+
resp = client.get("http://localhost:8080/stream/3")
|
|
125
|
+
chunks = []
|
|
126
|
+
resp.chunks { |chunk| chunks << chunk }
|
|
127
|
+
results[:t1] = chunks.size
|
|
128
|
+
done[:t1] = true
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
t2 = Thread.new do
|
|
132
|
+
resp = client.get("http://localhost:8080/stream/3")
|
|
133
|
+
chunks = []
|
|
134
|
+
resp.chunks { |chunk| chunks << chunk }
|
|
135
|
+
results[:t2] = chunks.size
|
|
136
|
+
done[:t2] = true
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
t1.join(15)
|
|
140
|
+
t2.join(15)
|
|
141
|
+
|
|
142
|
+
assert done[:t1], "Thread 1 should complete"
|
|
143
|
+
assert done[:t2], "Thread 2 should complete"
|
|
144
|
+
assert_equal 3, results[:t1], "Thread 1 should receive 3 chunks"
|
|
145
|
+
assert_equal 3, results[:t2], "Thread 2 should receive 3 chunks"
|
|
30
146
|
end
|
|
31
147
|
|
|
32
148
|
def test_thread_interrupt_connect
|
|
@@ -35,9 +151,11 @@ class StreamTest < Minitest::Test
|
|
|
35
151
|
Wreq.get(url)
|
|
36
152
|
rescue => _
|
|
37
153
|
end
|
|
154
|
+
|
|
38
155
|
sleep 2
|
|
39
156
|
thread.kill
|
|
40
157
|
killed = thread.join(5)
|
|
158
|
+
|
|
41
159
|
assert killed, "Connect phase should be interruptible"
|
|
42
160
|
end
|
|
43
161
|
|
|
@@ -47,9 +165,11 @@ class StreamTest < Minitest::Test
|
|
|
47
165
|
Wreq.get(url, timeout: 60)
|
|
48
166
|
rescue => _
|
|
49
167
|
end
|
|
168
|
+
|
|
50
169
|
sleep 2
|
|
51
170
|
thread.kill
|
|
52
171
|
killed = thread.join(5)
|
|
172
|
+
|
|
53
173
|
assert killed, "Connect+timeout phase should be interruptible"
|
|
54
174
|
end
|
|
55
175
|
|
|
@@ -60,9 +180,11 @@ class StreamTest < Minitest::Test
|
|
|
60
180
|
resp.text
|
|
61
181
|
rescue => _
|
|
62
182
|
end
|
|
183
|
+
|
|
63
184
|
sleep 2
|
|
64
185
|
thread.kill
|
|
65
186
|
killed = thread.join(5)
|
|
187
|
+
|
|
66
188
|
assert killed, "Body reading should be interruptible"
|
|
67
189
|
end
|
|
68
190
|
|
|
@@ -73,9 +195,177 @@ class StreamTest < Minitest::Test
|
|
|
73
195
|
resp.chunks { |chunk| chunk }
|
|
74
196
|
rescue => _
|
|
75
197
|
end
|
|
198
|
+
|
|
76
199
|
sleep 2
|
|
77
200
|
thread.kill
|
|
78
201
|
killed = thread.join(5)
|
|
202
|
+
|
|
79
203
|
assert killed, "Body streaming should be interruptible"
|
|
80
204
|
end
|
|
205
|
+
|
|
206
|
+
def test_thread_interrupt_during_slow_stream_with_block_processing
|
|
207
|
+
url = "http://localhost:8080/drip?duration=5&numbytes=5&delay=1"
|
|
208
|
+
thread = Thread.new do
|
|
209
|
+
resp = Wreq.get(url)
|
|
210
|
+
resp.chunks do |_chunk|
|
|
211
|
+
sleep 0.5
|
|
212
|
+
end
|
|
213
|
+
rescue => _
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
sleep 2
|
|
217
|
+
thread.kill
|
|
218
|
+
killed = thread.join(5)
|
|
219
|
+
|
|
220
|
+
assert killed, "Streaming with slow block processing should be interruptible"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def test_chunks_propagates_streaming_errors
|
|
224
|
+
client = Wreq::Client.new
|
|
225
|
+
resp = client.get("http://localhost:8080/drip?duration=10&numbytes=10", timeout: 1)
|
|
226
|
+
error_raised = false
|
|
227
|
+
|
|
228
|
+
begin
|
|
229
|
+
resp.chunks do |_chunk|
|
|
230
|
+
end
|
|
231
|
+
rescue => e
|
|
232
|
+
error_raised = true
|
|
233
|
+
assert(
|
|
234
|
+
e.is_a?(Wreq::TimeoutError) || e.is_a?(Wreq::BodyError) || e.is_a?(Wreq::ConnectionResetError),
|
|
235
|
+
"Expected a streaming error (TimeoutError/BodyError/ConnectionResetError), got #{e.class}: #{e.message}"
|
|
236
|
+
)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
assert error_raised, "A streaming error should have been raised for a timed-out drip response"
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def test_exception_in_block_propagates
|
|
243
|
+
client = Wreq::Client.new
|
|
244
|
+
resp = client.get("http://localhost:8080/stream/5")
|
|
245
|
+
error_raised = false
|
|
246
|
+
chunks_before_error = 0
|
|
247
|
+
|
|
248
|
+
begin
|
|
249
|
+
resp.chunks do |_chunk|
|
|
250
|
+
chunks_before_error += 1
|
|
251
|
+
raise "intentional error in block" if chunks_before_error == 2
|
|
252
|
+
end
|
|
253
|
+
rescue RuntimeError => e
|
|
254
|
+
error_raised = true
|
|
255
|
+
assert_equal "intentional error in block", e.message
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
assert error_raised, "Exception raised inside the block should propagate out"
|
|
259
|
+
assert_equal 2, chunks_before_error, "Should have processed 2 chunks before the error"
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def test_chunks_called_twice_raises_error
|
|
263
|
+
client = Wreq::Client.new
|
|
264
|
+
resp = client.get("http://localhost:8080/stream/3")
|
|
265
|
+
resp.chunks { |_chunk| }
|
|
266
|
+
error_raised = false
|
|
267
|
+
|
|
268
|
+
begin
|
|
269
|
+
resp.chunks { |_chunk| }
|
|
270
|
+
rescue => e
|
|
271
|
+
error_raised = true
|
|
272
|
+
assert_instance_of Wreq::MemoryError, e,
|
|
273
|
+
"Second chunks call should raise MemoryError, got #{e.class}: #{e.message}"
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
assert error_raised, "Second chunks call should raise an error"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def test_text_after_chunks_raises_error
|
|
280
|
+
client = Wreq::Client.new
|
|
281
|
+
resp = client.get("http://localhost:8080/stream/3")
|
|
282
|
+
resp.chunks { |_chunk| }
|
|
283
|
+
error_raised = false
|
|
284
|
+
|
|
285
|
+
begin
|
|
286
|
+
resp.text
|
|
287
|
+
rescue => e
|
|
288
|
+
error_raised = true
|
|
289
|
+
assert_instance_of Wreq::MemoryError, e,
|
|
290
|
+
"Calling text after chunks should raise MemoryError, got #{e.class}: #{e.message}"
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
assert error_raised, "Calling text after chunks should raise an error"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def test_chunks_content_matches_full_body
|
|
297
|
+
client = Wreq::Client.new
|
|
298
|
+
resp_full = client.get("http://localhost:8080/bytes/4096")
|
|
299
|
+
full_bytes = resp_full.bytes
|
|
300
|
+
|
|
301
|
+
resp_stream = client.get("http://localhost:8080/bytes/4096")
|
|
302
|
+
streamed_bytes = "".b
|
|
303
|
+
resp_stream.chunks do |chunk|
|
|
304
|
+
streamed_bytes << chunk
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
assert_equal full_bytes.bytesize, streamed_bytes.bytesize,
|
|
308
|
+
"Streamed body size should match full body size"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def test_chunks_json_stream_content
|
|
312
|
+
client = Wreq::Client.new
|
|
313
|
+
resp = client.get("http://localhost:8080/stream/5")
|
|
314
|
+
chunks = []
|
|
315
|
+
|
|
316
|
+
resp.chunks do |chunk|
|
|
317
|
+
chunks << chunk
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
chunks.each_with_index do |chunk, i|
|
|
321
|
+
assert_match(/\{.*\}/, chunk,
|
|
322
|
+
"Chunk #{i} should contain a JSON object, got: #{chunk[0..80]}")
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def test_block_not_garbage_collected_during_streaming
|
|
327
|
+
client = Wreq::Client.new
|
|
328
|
+
resp = client.get("http://localhost:8080/drip?duration=3&numbytes=3&delay=1")
|
|
329
|
+
chunks_received = 0
|
|
330
|
+
|
|
331
|
+
resp.chunks do |_chunk|
|
|
332
|
+
chunks_received += 1
|
|
333
|
+
GC.start
|
|
334
|
+
GC.start
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
assert_equal 3, chunks_received,
|
|
338
|
+
"All 3 chunks should be received even with forced GC between yields"
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def test_close_after_streaming
|
|
342
|
+
client = Wreq::Client.new
|
|
343
|
+
resp = client.get("http://localhost:8080/stream/3")
|
|
344
|
+
|
|
345
|
+
resp.chunks { |_chunk| }
|
|
346
|
+
resp.close
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def test_chunks_via_module_method
|
|
350
|
+
resp = Wreq.get("http://localhost:8080/stream/3")
|
|
351
|
+
chunks = []
|
|
352
|
+
|
|
353
|
+
resp.chunks do |chunk|
|
|
354
|
+
chunks << chunk
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
assert_equal 3, chunks.size, "Module-level Wreq.get + chunks should work"
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def test_chunks_via_client_instance
|
|
361
|
+
client = Wreq::Client.new
|
|
362
|
+
resp = client.get("http://localhost:8080/stream/3")
|
|
363
|
+
chunks = []
|
|
364
|
+
|
|
365
|
+
resp.chunks do |chunk|
|
|
366
|
+
chunks << chunk
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
assert_equal 3, chunks.size, "Client instance get + chunks should work"
|
|
370
|
+
end
|
|
81
371
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wreq
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SearchApi
|
|
@@ -29,7 +29,8 @@ files:
|
|
|
29
29
|
- build.rs
|
|
30
30
|
- examples/body.rb
|
|
31
31
|
- examples/client.rb
|
|
32
|
-
- examples/
|
|
32
|
+
- examples/cookie.rb
|
|
33
|
+
- examples/emulate_request.rb
|
|
33
34
|
- examples/headers.rb
|
|
34
35
|
- examples/proxy.rb
|
|
35
36
|
- examples/send_stream.rb
|
|
@@ -40,7 +41,7 @@ files:
|
|
|
40
41
|
- lib/wreq_ruby/body.rb
|
|
41
42
|
- lib/wreq_ruby/client.rb
|
|
42
43
|
- lib/wreq_ruby/cookie.rb
|
|
43
|
-
- lib/wreq_ruby/
|
|
44
|
+
- lib/wreq_ruby/emulate.rb
|
|
44
45
|
- lib/wreq_ruby/error.rb
|
|
45
46
|
- lib/wreq_ruby/header.rb
|
|
46
47
|
- lib/wreq_ruby/http.rb
|
|
@@ -71,6 +72,7 @@ files:
|
|
|
71
72
|
- test/emulation_test.rb
|
|
72
73
|
- test/error_handling_test.rb
|
|
73
74
|
- test/header_test.rb
|
|
75
|
+
- test/inspect_test.rb
|
|
74
76
|
- test/module_methods_test.rb
|
|
75
77
|
- test/request_parameters_test.rb
|
|
76
78
|
- test/request_test.rb
|