wreq 1.2.4 → 1.2.5

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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +3 -7
  3. data/Cargo.toml +6 -1
  4. data/crates/wreq-util/.github/FUNDING.yml +15 -0
  5. data/crates/wreq-util/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  6. data/crates/wreq-util/.github/ISSUE_TEMPLATE/custom.md +10 -0
  7. data/crates/wreq-util/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  8. data/crates/wreq-util/.github/dependabot.yml +22 -0
  9. data/crates/wreq-util/.github/workflows/ci.yml +82 -0
  10. data/crates/wreq-util/.github/workflows/release-plz.yml +52 -0
  11. data/crates/wreq-util/.gitignore +24 -0
  12. data/crates/wreq-util/CHANGELOG.md +74 -0
  13. data/crates/wreq-util/Cargo.toml +94 -0
  14. data/crates/wreq-util/LICENSE +201 -0
  15. data/crates/wreq-util/README.md +62 -0
  16. data/crates/wreq-util/examples/emulate.rs +39 -0
  17. data/crates/wreq-util/examples/tower_delay.rs +191 -0
  18. data/crates/wreq-util/release-plz.toml +2 -0
  19. data/crates/wreq-util/rustfmt.toml +5 -0
  20. data/crates/wreq-util/src/emulate/compress.rs +85 -0
  21. data/crates/wreq-util/src/emulate/macros.rs +370 -0
  22. data/crates/wreq-util/src/emulate/profile/chrome/header.rs +59 -0
  23. data/crates/wreq-util/src/emulate/profile/chrome/http2.rs +65 -0
  24. data/crates/wreq-util/src/emulate/profile/chrome/tls.rs +153 -0
  25. data/crates/wreq-util/src/emulate/profile/chrome.rs +2028 -0
  26. data/crates/wreq-util/src/emulate/profile/firefox/header.rs +21 -0
  27. data/crates/wreq-util/src/emulate/profile/firefox/http2.rs +113 -0
  28. data/crates/wreq-util/src/emulate/profile/firefox/tls.rs +253 -0
  29. data/crates/wreq-util/src/emulate/profile/firefox.rs +518 -0
  30. data/crates/wreq-util/src/emulate/profile/okhttp.rs +241 -0
  31. data/crates/wreq-util/src/emulate/profile/opera/header.rs +30 -0
  32. data/crates/wreq-util/src/emulate/profile/opera/http2.rs +50 -0
  33. data/crates/wreq-util/src/emulate/profile/opera/tls.rs +97 -0
  34. data/crates/wreq-util/src/emulate/profile/opera.rs +299 -0
  35. data/crates/wreq-util/src/emulate/profile/safari/header.rs +59 -0
  36. data/crates/wreq-util/src/emulate/profile/safari/http2.rs +116 -0
  37. data/crates/wreq-util/src/emulate/profile/safari/tls.rs +177 -0
  38. data/crates/wreq-util/src/emulate/profile/safari.rs +222 -0
  39. data/crates/wreq-util/src/emulate/profile.rs +47 -0
  40. data/crates/wreq-util/src/emulate.rs +369 -0
  41. data/crates/wreq-util/src/lib.rs +14 -0
  42. data/crates/wreq-util/src/rand.rs +24 -0
  43. data/crates/wreq-util/src/tower/delay/future.rs +43 -0
  44. data/crates/wreq-util/src/tower/delay/layer.rs +201 -0
  45. data/crates/wreq-util/src/tower/delay/service.rs +190 -0
  46. data/crates/wreq-util/src/tower/delay.rs +98 -0
  47. data/crates/wreq-util/src/tower.rs +7 -0
  48. data/crates/wreq-util/tests/client.rs +51 -0
  49. data/crates/wreq-util/tests/emulate_chrome.rs +270 -0
  50. data/crates/wreq-util/tests/emulate_firefox.rs +92 -0
  51. data/crates/wreq-util/tests/emulate_okhttp.rs +46 -0
  52. data/crates/wreq-util/tests/emulate_opera.rs +113 -0
  53. data/crates/wreq-util/tests/emulate_safari.rs +151 -0
  54. data/crates/wreq-util/tests/support/mod.rs +55 -0
  55. data/crates/wreq-util/tests/support/server.rs +232 -0
  56. data/examples/emulate_request.rb +1 -1
  57. data/lib/wreq.rb +20 -18
  58. data/lib/wreq_ruby/body.rb +3 -0
  59. data/lib/wreq_ruby/client.rb +18 -18
  60. data/lib/wreq_ruby/cookie.rb +2 -2
  61. data/lib/wreq_ruby/emulate.rb +3 -1
  62. data/lib/wreq_ruby/header.rb +205 -114
  63. data/lib/wreq_ruby/response.rb +28 -3
  64. data/src/emulate.rs +1 -0
  65. data/src/error.rs +2 -6
  66. data/src/header/helper.rs +112 -0
  67. data/src/header.rs +198 -133
  68. data/test/header_test.rb +228 -192
  69. data/test/stream_test.rb +36 -0
  70. metadata +54 -1
data/test/header_test.rb CHANGED
@@ -2,289 +2,325 @@ require "test_helper"
2
2
 
3
3
  class HeadersTest < Minitest::Test
4
4
  def setup
5
- @response = Wreq.get("#{HTTPBIN_URL}/response-headers",
6
- query: {
7
- "X-Custom-Header" => "custom-value",
8
- "X-Multi-Header" => "value1"
9
- })
10
- @headers = @response.headers
5
+ @headers = Wreq::Headers.new(
6
+ "Content-Type" => "application/json",
7
+ "X-Custom-Header" => "custom-value"
8
+ )
11
9
  end
12
10
 
13
- def test_headers_class
14
- assert_instance_of Wreq::Headers, @headers
15
- end
16
-
17
- def test_initialize
11
+ def test_headers_class_and_empty_constructor
18
12
  headers = Wreq::Headers.new
13
+
19
14
  assert_instance_of Wreq::Headers, headers
20
15
  assert headers.empty?
21
16
  assert_equal 0, headers.length
17
+ assert_includes Wreq::Headers.ancestors, Enumerable
22
18
  end
23
19
 
24
- def test_get_existing_header
25
- # Content-Type should exist in response
26
- content_type = @headers.get("Content-Type")
27
- assert_instance_of String, content_type
28
- refute_nil content_type
20
+ def test_collection_api_methods_are_available
21
+ collection_methods = [:[], :[]=, :fetch, :delete, :size, :to_a, :to_h, :to_hash, :each]
22
+
23
+ collection_methods.each do |method|
24
+ assert_respond_to @headers, method
25
+ end
29
26
  end
30
27
 
31
- def test_get_case_insensitive
32
- # Test case-insensitive lookup
33
- value1 = @headers.get("content-type")
34
- value2 = @headers.get("Content-Type")
35
- value3 = @headers.get("CONTENT-TYPE")
28
+ def test_initialize_from_hash_and_headers
29
+ headers = Wreq::Headers.new("Accept" => "application/json")
30
+ copy = Wreq::Headers.new(headers)
36
31
 
37
- assert_equal value1, value2
38
- assert_equal value2, value3
39
- end
32
+ headers["Accept"] = "text/plain"
40
33
 
41
- def test_get_nonexistent_header
42
- result = @headers.get("X-Nonexistent-Header-12345")
43
- assert_nil result
34
+ assert_equal "text/plain", headers["Accept"]
35
+ assert_equal "application/json", copy["Accept"]
44
36
  end
45
37
 
46
- def test_get_all_single_value
47
- # Most headers have single values
48
- values = @headers.get_all("Content-Type")
49
- assert_instance_of Array, values
50
- assert_equal 1, values.length
51
- assert_equal @headers.get("Content-Type"), values.first
38
+ def test_initialize_from_enumerable_pairs
39
+ source = Class.new do
40
+ include Enumerable
41
+
42
+ def each
43
+ yield "X-First", "one"
44
+ yield :set_cookie, ["a=1", "b=2"]
45
+ end
46
+ end.new
47
+
48
+ headers = Wreq::Headers.new(source)
49
+
50
+ assert_includes headers.keys, "x-first"
51
+ assert_includes headers.keys, "set-cookie"
52
+ assert_equal ["a=1", "b=2"], headers[:set_cookie]
52
53
  end
53
54
 
54
- def test_get_all_nonexistent
55
- values = @headers.get_all("X-Nonexistent-Header")
56
- assert_instance_of Array, values
57
- assert_equal 0, values.length
58
- assert_empty values
55
+ def test_initialize_rejects_invalid_sources_and_pairs
56
+ assert_raises(Wreq::BuilderError) { Wreq::Headers.new(Object.new) }
57
+ assert_raises(Wreq::BuilderError) { Wreq::Headers.new([["Accept"]]) }
58
+ assert_raises(ArgumentError) { Wreq::Headers.new({}, {}) }
59
59
  end
60
60
 
61
- def test_set_new_header
62
- headers = Wreq::Headers.new
63
- headers.set("X-Test-Header", "test-value")
61
+ def test_string_and_symbol_names_are_normalized
62
+ headers = Wreq::Headers.new(
63
+ "x-CuStOm-Header" => "value",
64
+ content_type: "application/json"
65
+ )
64
66
 
65
- assert_equal "test-value", headers.get("X-Test-Header")
66
- assert_equal 1, headers.length
67
+ assert_includes headers.keys, "x-custom-header"
68
+ assert_includes headers.keys, "content-type"
69
+ assert_equal "value", headers["X-CUSTOM-HEADER"]
70
+ assert_equal "application/json", headers[:content_type]
67
71
  end
68
72
 
69
- def test_set_replaces_existing
70
- headers = Wreq::Headers.new
71
- headers.set("X-Test", "value1")
72
- headers.set("X-Test", "value2")
73
+ def test_get_returns_first_value
74
+ headers = Wreq::Headers.new("Accept" => ["application/json", "text/plain"])
73
75
 
74
- assert_equal "value2", headers.get("X-Test")
75
- values = headers.get_all("X-Test")
76
- assert_equal 1, values.length
77
- assert_equal "value2", values.first
76
+ assert_equal "application/json", headers.get("accept")
77
+ assert_equal "application/json", headers.get(:accept)
78
+ assert_nil headers.get(:missing)
78
79
  end
79
80
 
80
- def test_append_to_new_header
81
- headers = Wreq::Headers.new
82
- headers.append("Accept", "application/json")
81
+ def test_index_uses_nil_string_and_array_shapes
82
+ headers = Wreq::Headers.new(
83
+ "Accept" => "application/json",
84
+ "Set-Cookie" => ["a=1", "b=2"]
85
+ )
83
86
 
84
- assert_equal "application/json", headers.get("Accept")
87
+ assert_nil headers["Missing"]
88
+ assert_equal "application/json", headers["Accept"]
89
+ assert_equal ["a=1", "b=2"], headers["Set-Cookie"]
85
90
  end
86
91
 
87
- def test_append_to_existing_header
88
- headers = Wreq::Headers.new
89
- headers.set("Accept", "application/json")
90
- headers.append("Accept", "text/html")
91
- headers.append("Accept", "application/xml")
92
+ def test_get_all_always_returns_an_array
93
+ assert_equal ["application/json"], @headers.get_all("CONTENT-TYPE")
94
+ assert_equal [], @headers.get_all("Missing")
95
+ end
96
+
97
+ def test_set_replaces_existing_occurrences
98
+ headers = Wreq::Headers.new("Accept" => ["application/json", "text/plain"])
99
+
100
+ headers.set("Accept", ["text/html", "application/xml"])
92
101
 
93
- values = headers.get_all("Accept")
94
- assert_equal 3, values.length
95
- assert_includes values, "application/json"
96
- assert_includes values, "text/html"
97
- assert_includes values, "application/xml"
102
+ assert_equal ["text/html", "application/xml"], headers.get_all("Accept")
103
+ assert_equal 2, headers.length
98
104
  end
99
105
 
100
- def test_remove_existing_header
101
- headers = Wreq::Headers.new
102
- headers.set("X-Remove-Me", "value")
106
+ def test_index_assignment_replaces_existing_occurrences
107
+ headers = Wreq::Headers.new("Set-Cookie" => "old=1")
103
108
 
104
- removed_value = headers.remove("X-Remove-Me")
105
- assert_equal "value", removed_value
106
- assert_nil headers.get("X-Remove-Me")
109
+ assigned = headers.public_send(:[]=, :set_cookie, ["a=1", "b=2"])
110
+
111
+ assert_equal ["a=1", "b=2"], assigned
112
+ assert_equal ["a=1", "b=2"], headers.get_all("Set-Cookie")
107
113
  end
108
114
 
109
- def test_remove_nonexistent_header
115
+ def test_append_keeps_values_as_separate_occurrences
110
116
  headers = Wreq::Headers.new
111
- result = headers.remove("X-Nonexistent")
112
- assert_nil result
117
+ headers.append("Set-Cookie", "a=1")
118
+ headers.append("Set-Cookie", ["b=2", "c=3"])
119
+
120
+ assert_equal ["a=1", "b=2", "c=3"], headers.get_all("Set-Cookie")
121
+ refute_includes headers.get_all("Set-Cookie"), "a=1,b=2,c=3"
113
122
  end
114
123
 
115
- def test_delete_alias
124
+ def test_set_and_append_return_nil
116
125
  headers = Wreq::Headers.new
117
- headers.set("X-Delete-Me", "value")
118
126
 
119
- removed_value = headers.remove("X-Delete-Me")
120
- assert_equal "value", removed_value
121
- assert_nil headers.get("X-Delete-Me")
127
+ assert_nil headers.set("Accept", "application/json")
128
+ assert_nil headers.append("Accept", "text/plain")
122
129
  end
123
130
 
124
- def test_contains_existing
125
- assert @headers.contains?("Content-Type")
126
- end
131
+ def test_empty_array_values_remove_or_leave_headers_unchanged
132
+ headers = Wreq::Headers.new("Accept" => "application/json")
133
+
134
+ assert_nil headers.set("Accept", [])
135
+ refute headers.key?("Accept")
127
136
 
128
- def test_contains_nonexistent
129
- refute @headers.contains?("X-Nonexistent-Header-12345")
137
+ assert_nil headers.append("X-Empty", [])
138
+ refute headers.key?("X-Empty")
130
139
  end
131
140
 
132
- def test_contains_case_insensitive
133
- # If Content-Type exists
134
- if @headers.contains?("Content-Type")
135
- assert @headers.contains?("content-type")
136
- assert @headers.contains?("CONTENT-TYPE")
137
- end
141
+ def test_fetch_existing_and_missing_values
142
+ assert_equal "application/json", @headers.fetch(:content_type)
143
+ assert_equal "fallback", @headers.fetch("Missing", "fallback")
144
+ assert_equal "MISSING", @headers.fetch("Missing") { |name| name.upcase }
145
+ assert_raises(KeyError) { @headers.fetch("Missing") }
138
146
  end
139
147
 
140
- def test_key_alias
141
- # key? is an alias for contains?
142
- assert_equal @headers.contains?("Content-Type"), @headers.key?("Content-Type")
148
+ def test_fetch_block_takes_precedence_over_default
149
+ result = @headers.fetch("Missing", "fallback") { "from block" }
150
+
151
+ assert_equal "from block", result
143
152
  end
144
153
 
145
- def test_length
146
- headers = Wreq::Headers.new
147
- assert_equal 0, headers.length
154
+ def test_fetch_preserves_repeated_values_and_explicit_nil_default
155
+ headers = Wreq::Headers.new("Set-Cookie" => ["a=1", "b=2"])
148
156
 
149
- headers.set("Header1", "value1")
150
- assert_equal 1, headers.length
157
+ assert_equal ["a=1", "b=2"], headers.fetch(:set_cookie)
158
+ assert_nil headers.fetch("Missing", nil)
159
+ end
151
160
 
152
- headers.set("Header2", "value2")
153
- assert_equal 2, headers.length
161
+ def test_remove_and_delete_remove_every_occurrence
162
+ headers = Wreq::Headers.new("Set-Cookie" => ["a=1", "b=2"])
154
163
 
155
- # Setting same header shouldn't increase length
156
- headers.set("Header1", "new-value")
157
- assert_equal 2, headers.length
164
+ assert_equal "a=1", headers.delete(:set_cookie)
165
+ assert_nil headers["Set-Cookie"]
166
+ assert_nil headers.remove("Set-Cookie")
158
167
  end
159
168
 
160
- def test_empty_on_new_headers
161
- headers = Wreq::Headers.new
162
- assert headers.empty?
169
+ def test_contains_and_key_are_case_insensitive
170
+ assert @headers.contains?("CONTENT-TYPE")
171
+ assert @headers.contains?(:content_type)
172
+ assert @headers.key?("content-type")
173
+ refute @headers.key?("Missing")
163
174
  end
164
175
 
165
- def test_empty_on_headers_with_data
166
- refute @headers.empty?
176
+ def test_length_counts_occurrences_and_keys_are_unique
177
+ headers = Wreq::Headers.new(
178
+ "Accept" => ["application/json", "text/plain"],
179
+ "Content-Type" => "application/json"
180
+ )
181
+
182
+ assert_equal 3, headers.length
183
+ assert_equal 3, headers.size
184
+ assert_equal 2, headers.keys.length
167
185
  end
168
186
 
169
- def test_clear
170
- headers = Wreq::Headers.new
171
- headers.set("Header1", "value1")
172
- headers.set("Header2", "value2")
187
+ def test_clear_returns_self
188
+ headers = Wreq::Headers.new("Accept" => "application/json")
173
189
 
174
- refute headers.empty?
175
- headers.clear
190
+ assert_same headers, headers.clear
176
191
  assert headers.empty?
177
- assert_equal 0, headers.length
178
192
  end
179
193
 
180
- def test_keys
181
- headers = Wreq::Headers.new
182
- headers.set("Content-Type", "application/json")
183
- headers.set("Authorization", "Bearer token")
194
+ def test_values_include_every_occurrence
195
+ headers = Wreq::Headers.new("Accept" => ["application/json", "text/plain"])
184
196
 
185
- keys = headers.keys
186
- assert_instance_of Array, keys
187
- assert_equal 2, keys.length
188
- assert_includes keys, "content-type"
189
- assert_includes keys, "authorization"
197
+ assert_equal ["application/json", "text/plain"], headers.values
190
198
  end
191
199
 
192
- def test_keys_are_lowercase
193
- headers = Wreq::Headers.new
194
- headers.set("Content-Type", "text/html")
195
- headers.set("X-Custom-Header", "value")
200
+ def test_each_yields_every_occurrence_and_returns_self
201
+ headers = Wreq::Headers.new("Set-Cookie" => ["a=1", "b=2"])
202
+ pairs = []
196
203
 
197
- keys = headers.keys
198
- keys.each do |key|
199
- assert_equal key, key.downcase
200
- end
204
+ returned = headers.each { |name, value| pairs << [name, value] }
205
+
206
+ assert_same headers, returned
207
+ assert_equal [["set-cookie", "a=1"], ["set-cookie", "b=2"]], pairs
201
208
  end
202
209
 
203
- def test_values
204
- headers = Wreq::Headers.new
205
- headers.set("Content-Type", "application/json")
206
- headers.set("Authorization", "Bearer token")
210
+ def test_each_without_a_block_returns_chainable_enumerator
211
+ headers = Wreq::Headers.new(
212
+ "Accept" => "application/json",
213
+ "Set-Cookie" => ["a=1", "b=2"]
214
+ )
215
+
216
+ enumerator = headers.each
217
+ cookies = enumerator.select { |name, _value| name == "set-cookie" }
207
218
 
208
- values = headers.values
209
- assert_instance_of Array, values
210
- assert_equal 2, values.length
211
- assert_includes values, "application/json"
212
- assert_includes values, "Bearer token"
219
+ assert_instance_of Enumerator, enumerator
220
+ assert_equal [["set-cookie", "a=1"], ["set-cookie", "b=2"]], cookies
213
221
  end
214
222
 
215
- def test_each_with_block
216
- headers = Wreq::Headers.new
217
- headers.set("Header1", "value1")
218
- headers.set("Header2", "value2")
219
-
220
- collected = {}
221
- headers.each do |name, value|
222
- assert_instance_of String, name
223
- assert_instance_of String, value
224
- collected[name] = value
223
+ def test_each_allows_mutating_headers_from_the_block
224
+ headers = Wreq::Headers.new("X-First" => "one")
225
+ yielded_names = []
226
+
227
+ headers.each do |name, _value|
228
+ yielded_names << name
229
+ headers["X-Added"] = "two"
225
230
  end
226
231
 
227
- assert_equal 2, collected.length
228
- assert_equal "value1", collected["header1"]
229
- assert_equal "value2", collected["header2"]
232
+ assert_equal ["x-first"], yielded_names
233
+ assert_equal "two", headers["X-Added"]
230
234
  end
231
235
 
232
- def test_multiple_operations_sequence
233
- headers = Wreq::Headers.new
236
+ def test_enumerable_methods_use_each
237
+ headers = Wreq::Headers.new(
238
+ "Accept" => "application/json",
239
+ "Set-Cookie" => ["a=1", "b=2"]
240
+ )
234
241
 
235
- # Add headers
236
- headers.set("Content-Type", "application/json")
237
- headers.set("Accept", "application/json")
242
+ cookies = headers.select { |name, _value| name == "set-cookie" }
238
243
 
239
- assert_equal 2, headers.length
244
+ assert_equal [["set-cookie", "a=1"], ["set-cookie", "b=2"]], cookies
245
+ end
240
246
 
241
- # Append to Accept
242
- headers.append("Accept", "text/html")
243
- assert_equal 2, headers.get_all("Accept").length
247
+ def test_to_a_and_to_h_preserve_duplicate_values
248
+ headers = Wreq::Headers.new(
249
+ "Accept" => "application/json",
250
+ "Set-Cookie" => ["a=1", "b=2"]
251
+ )
244
252
 
245
- # Clear all
246
- headers.clear
247
- assert headers.empty?
253
+ expected_pairs = [
254
+ ["accept", "application/json"],
255
+ ["set-cookie", "a=1"],
256
+ ["set-cookie", "b=2"]
257
+ ]
258
+ assert_equal expected_pairs.sort, headers.to_a.sort
259
+ assert_equal({
260
+ "accept" => "application/json",
261
+ "set-cookie" => ["a=1", "b=2"]
262
+ }, headers.to_h)
263
+ assert_equal headers.to_h, headers.to_hash
248
264
  end
249
265
 
250
266
  def test_special_characters_in_header_values
267
+ value = "Bearer token-123_abc/xyz+456=789"
268
+ @headers.set("Authorization", value)
269
+
270
+ assert_equal value, @headers.get("Authorization")
271
+ end
272
+
273
+ def test_invalid_header_names_and_values_raise_builder_error
251
274
  headers = Wreq::Headers.new
252
- special_value = "Bearer token-123_abc/xyz+456=789"
253
- headers.set("Authorization", special_value)
254
275
 
255
- assert_equal special_value, headers.get("Authorization")
276
+ assert_raises(Wreq::BuilderError) { headers.set(123, "value") }
277
+ assert_raises(Wreq::BuilderError) { headers.set("Bad\nName", "value") }
278
+ assert_raises(Wreq::BuilderError) { headers.set("X-Test", 123) }
279
+ assert_raises(Wreq::BuilderError) { headers.append("X-Test", ["valid", 123]) }
280
+ assert headers.empty?
256
281
  end
257
282
 
258
- def test_response_headers_integration
259
- # Test that headers from actual HTTP response work correctly
260
- assert_instance_of Wreq::Headers, @headers
261
- refute @headers.empty?
283
+ def test_header_entry_limit_raises_builder_error_without_partial_update
284
+ headers = Wreq::Headers.new
285
+ values = Array.new(32_769, "value")
262
286
 
263
- # Should have common HTTP headers
264
- assert @headers.length > 0
287
+ error = assert_raises(Wreq::BuilderError) { headers.append("X-Large", values) }
288
+
289
+ assert_match(/32,768/, error.message)
290
+ assert headers.empty?
265
291
  end
266
292
 
267
- def test_response_headers_each
268
- # Test iteration over real response headers
269
- count = 0
270
- @headers.each do |name, value|
271
- assert_instance_of String, name
272
- assert_instance_of String, value
273
- count += 1
274
- end
293
+ def test_response_headers_integration
294
+ headers = response_headers
295
+ pairs = headers.each.to_a
275
296
 
276
- assert count > 0
277
- assert_equal @headers.length, count
297
+ assert_instance_of Wreq::Headers, headers
298
+ refute headers.empty?
299
+ assert headers.contains?("Content-Type")
300
+ assert_equal headers.length, pairs.length
278
301
  end
279
302
 
280
- def test_headers_immutability_across_instances
281
- headers1 = Wreq::Headers.new
282
- headers2 = Wreq::Headers.new
303
+ def test_response_headers_are_fresh_mutable_snapshots
304
+ response = Wreq.get("#{HTTPBIN_URL}/response-headers", query: {"X-Test" => "original"})
305
+ first = response.headers
306
+ second = response.headers
307
+
308
+ refute_same first, second
309
+ first["X-Test"] = "changed"
310
+ first["X-Local"] = "value"
311
+
312
+ assert_equal "changed", first["X-Test"]
313
+ assert_equal "original", second["X-Test"]
314
+ assert_nil second["X-Local"]
315
+ assert_equal "original", response.headers["X-Test"]
316
+ end
283
317
 
284
- headers1.set("X-Test", "value1")
285
- headers2.set("X-Test", "value2")
318
+ private
286
319
 
287
- assert_equal "value1", headers1.get("X-Test")
288
- assert_equal "value2", headers2.get("X-Test")
320
+ def response_headers
321
+ Wreq.get(
322
+ "#{HTTPBIN_URL}/response-headers",
323
+ query: {"X-Custom-Header" => "custom-value"}
324
+ ).headers
289
325
  end
290
326
  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
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.2.4
4
+ version: 1.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - SearchApi
@@ -27,6 +27,58 @@ files:
27
27
  - README.md
28
28
  - Rakefile
29
29
  - build.rs
30
+ - crates/wreq-util/.github/FUNDING.yml
31
+ - crates/wreq-util/.github/ISSUE_TEMPLATE/bug_report.md
32
+ - crates/wreq-util/.github/ISSUE_TEMPLATE/custom.md
33
+ - crates/wreq-util/.github/ISSUE_TEMPLATE/feature_request.md
34
+ - crates/wreq-util/.github/dependabot.yml
35
+ - crates/wreq-util/.github/workflows/ci.yml
36
+ - crates/wreq-util/.github/workflows/release-plz.yml
37
+ - crates/wreq-util/.gitignore
38
+ - crates/wreq-util/CHANGELOG.md
39
+ - crates/wreq-util/Cargo.toml
40
+ - crates/wreq-util/LICENSE
41
+ - crates/wreq-util/README.md
42
+ - crates/wreq-util/examples/emulate.rs
43
+ - crates/wreq-util/examples/tower_delay.rs
44
+ - crates/wreq-util/release-plz.toml
45
+ - crates/wreq-util/rustfmt.toml
46
+ - crates/wreq-util/src/emulate.rs
47
+ - crates/wreq-util/src/emulate/compress.rs
48
+ - crates/wreq-util/src/emulate/macros.rs
49
+ - crates/wreq-util/src/emulate/profile.rs
50
+ - crates/wreq-util/src/emulate/profile/chrome.rs
51
+ - crates/wreq-util/src/emulate/profile/chrome/header.rs
52
+ - crates/wreq-util/src/emulate/profile/chrome/http2.rs
53
+ - crates/wreq-util/src/emulate/profile/chrome/tls.rs
54
+ - crates/wreq-util/src/emulate/profile/firefox.rs
55
+ - crates/wreq-util/src/emulate/profile/firefox/header.rs
56
+ - crates/wreq-util/src/emulate/profile/firefox/http2.rs
57
+ - crates/wreq-util/src/emulate/profile/firefox/tls.rs
58
+ - crates/wreq-util/src/emulate/profile/okhttp.rs
59
+ - crates/wreq-util/src/emulate/profile/opera.rs
60
+ - crates/wreq-util/src/emulate/profile/opera/header.rs
61
+ - crates/wreq-util/src/emulate/profile/opera/http2.rs
62
+ - crates/wreq-util/src/emulate/profile/opera/tls.rs
63
+ - crates/wreq-util/src/emulate/profile/safari.rs
64
+ - crates/wreq-util/src/emulate/profile/safari/header.rs
65
+ - crates/wreq-util/src/emulate/profile/safari/http2.rs
66
+ - crates/wreq-util/src/emulate/profile/safari/tls.rs
67
+ - crates/wreq-util/src/lib.rs
68
+ - crates/wreq-util/src/rand.rs
69
+ - crates/wreq-util/src/tower.rs
70
+ - crates/wreq-util/src/tower/delay.rs
71
+ - crates/wreq-util/src/tower/delay/future.rs
72
+ - crates/wreq-util/src/tower/delay/layer.rs
73
+ - crates/wreq-util/src/tower/delay/service.rs
74
+ - crates/wreq-util/tests/client.rs
75
+ - crates/wreq-util/tests/emulate_chrome.rs
76
+ - crates/wreq-util/tests/emulate_firefox.rs
77
+ - crates/wreq-util/tests/emulate_okhttp.rs
78
+ - crates/wreq-util/tests/emulate_opera.rs
79
+ - crates/wreq-util/tests/emulate_safari.rs
80
+ - crates/wreq-util/tests/support/mod.rs
81
+ - crates/wreq-util/tests/support/server.rs
30
82
  - docs/windows-gnu-tokio-crash.md
31
83
  - examples/body.rb
32
84
  - examples/client.rb
@@ -65,6 +117,7 @@ files:
65
117
  - src/extractor.rs
66
118
  - src/gvl.rs
67
119
  - src/header.rs
120
+ - src/header/helper.rs
68
121
  - src/http.rs
69
122
  - src/lib.rs
70
123
  - src/macros.rs