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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +110 -13
  5. data/UPGRADING.md +491 -0
  6. data/http.gemspec +32 -29
  7. data/lib/http/base64.rb +11 -1
  8. data/lib/http/chainable/helpers.rb +62 -0
  9. data/lib/http/chainable/verbs.rb +136 -0
  10. data/lib/http/chainable.rb +232 -136
  11. data/lib/http/client.rb +158 -127
  12. data/lib/http/connection/internals.rb +141 -0
  13. data/lib/http/connection.rb +126 -97
  14. data/lib/http/content_type.rb +61 -6
  15. data/lib/http/errors.rb +25 -1
  16. data/lib/http/feature.rb +65 -5
  17. data/lib/http/features/auto_deflate.rb +124 -17
  18. data/lib/http/features/auto_inflate.rb +38 -15
  19. data/lib/http/features/caching/entry.rb +178 -0
  20. data/lib/http/features/caching/in_memory_store.rb +63 -0
  21. data/lib/http/features/caching.rb +216 -0
  22. data/lib/http/features/digest_auth.rb +234 -0
  23. data/lib/http/features/instrumentation.rb +97 -17
  24. data/lib/http/features/logging.rb +183 -5
  25. data/lib/http/features/normalize_uri.rb +17 -0
  26. data/lib/http/features/raise_error.rb +18 -3
  27. data/lib/http/form_data/composite_io.rb +106 -0
  28. data/lib/http/form_data/file.rb +95 -0
  29. data/lib/http/form_data/multipart/param.rb +62 -0
  30. data/lib/http/form_data/multipart.rb +106 -0
  31. data/lib/http/form_data/part.rb +52 -0
  32. data/lib/http/form_data/readable.rb +58 -0
  33. data/lib/http/form_data/urlencoded.rb +175 -0
  34. data/lib/http/form_data/version.rb +8 -0
  35. data/lib/http/form_data.rb +102 -0
  36. data/lib/http/headers/known.rb +3 -0
  37. data/lib/http/headers/normalizer.rb +17 -36
  38. data/lib/http/headers.rb +172 -65
  39. data/lib/http/mime_type/adapter.rb +24 -9
  40. data/lib/http/mime_type/json.rb +19 -4
  41. data/lib/http/mime_type.rb +21 -3
  42. data/lib/http/options/definitions.rb +189 -0
  43. data/lib/http/options.rb +172 -125
  44. data/lib/http/redirector.rb +80 -75
  45. data/lib/http/request/body.rb +87 -6
  46. data/lib/http/request/builder.rb +184 -0
  47. data/lib/http/request/proxy.rb +83 -0
  48. data/lib/http/request/writer.rb +76 -16
  49. data/lib/http/request.rb +214 -98
  50. data/lib/http/response/body.rb +103 -18
  51. data/lib/http/response/inflater.rb +35 -7
  52. data/lib/http/response/parser.rb +98 -4
  53. data/lib/http/response/status/reasons.rb +2 -4
  54. data/lib/http/response/status.rb +141 -31
  55. data/lib/http/response.rb +219 -61
  56. data/lib/http/retriable/delay_calculator.rb +38 -11
  57. data/lib/http/retriable/errors.rb +21 -0
  58. data/lib/http/retriable/performer.rb +82 -38
  59. data/lib/http/session.rb +280 -0
  60. data/lib/http/timeout/global.rb +147 -34
  61. data/lib/http/timeout/null.rb +155 -9
  62. data/lib/http/timeout/per_operation.rb +139 -18
  63. data/lib/http/uri/normalizer.rb +82 -0
  64. data/lib/http/uri/parsing.rb +182 -0
  65. data/lib/http/uri.rb +289 -124
  66. data/lib/http/version.rb +2 -1
  67. data/lib/http.rb +11 -2
  68. data/sig/deps.rbs +122 -0
  69. data/sig/http.rbs +1619 -0
  70. data/test/http/base64_test.rb +28 -0
  71. data/test/http/client_test.rb +739 -0
  72. data/test/http/connection_test.rb +1533 -0
  73. data/test/http/content_type_test.rb +190 -0
  74. data/test/http/errors_test.rb +28 -0
  75. data/test/http/feature_test.rb +49 -0
  76. data/test/http/features/auto_deflate_test.rb +317 -0
  77. data/test/http/features/auto_inflate_test.rb +213 -0
  78. data/test/http/features/caching_test.rb +942 -0
  79. data/test/http/features/digest_auth_test.rb +996 -0
  80. data/test/http/features/instrumentation_test.rb +246 -0
  81. data/test/http/features/logging_test.rb +654 -0
  82. data/test/http/features/normalize_uri_test.rb +41 -0
  83. data/test/http/features/raise_error_test.rb +77 -0
  84. data/test/http/form_data/composite_io_test.rb +215 -0
  85. data/test/http/form_data/file_test.rb +255 -0
  86. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  87. data/test/http/form_data/multipart_test.rb +303 -0
  88. data/test/http/form_data/part_test.rb +90 -0
  89. data/test/http/form_data/urlencoded_test.rb +164 -0
  90. data/test/http/form_data_test.rb +232 -0
  91. data/test/http/headers/normalizer_test.rb +93 -0
  92. data/test/http/headers_test.rb +888 -0
  93. data/test/http/mime_type/json_test.rb +39 -0
  94. data/test/http/mime_type_test.rb +150 -0
  95. data/test/http/options/base_uri_test.rb +148 -0
  96. data/test/http/options/body_test.rb +21 -0
  97. data/test/http/options/features_test.rb +38 -0
  98. data/test/http/options/form_test.rb +21 -0
  99. data/test/http/options/headers_test.rb +32 -0
  100. data/test/http/options/json_test.rb +21 -0
  101. data/test/http/options/merge_test.rb +78 -0
  102. data/test/http/options/new_test.rb +37 -0
  103. data/test/http/options/proxy_test.rb +32 -0
  104. data/test/http/options_test.rb +575 -0
  105. data/test/http/redirector_test.rb +639 -0
  106. data/test/http/request/body_test.rb +318 -0
  107. data/test/http/request/builder_test.rb +623 -0
  108. data/test/http/request/writer_test.rb +391 -0
  109. data/test/http/request_test.rb +1733 -0
  110. data/test/http/response/body_test.rb +292 -0
  111. data/test/http/response/parser_test.rb +105 -0
  112. data/test/http/response/status_test.rb +322 -0
  113. data/test/http/response_test.rb +502 -0
  114. data/test/http/retriable/delay_calculator_test.rb +194 -0
  115. data/test/http/retriable/errors_test.rb +71 -0
  116. data/test/http/retriable/performer_test.rb +551 -0
  117. data/test/http/session_test.rb +424 -0
  118. data/test/http/timeout/global_test.rb +239 -0
  119. data/test/http/timeout/null_test.rb +218 -0
  120. data/test/http/timeout/per_operation_test.rb +220 -0
  121. data/test/http/uri/normalizer_test.rb +89 -0
  122. data/test/http/uri_test.rb +1140 -0
  123. data/test/http/version_test.rb +15 -0
  124. data/test/http_test.rb +818 -0
  125. data/test/regression_tests.rb +27 -0
  126. data/test/support/dummy_server/encoding_routes.rb +47 -0
  127. data/test/support/dummy_server/routes.rb +201 -0
  128. data/test/support/dummy_server/servlet.rb +81 -0
  129. data/test/support/dummy_server.rb +200 -0
  130. data/{spec → test}/support/fakeio.rb +2 -2
  131. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  132. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  133. data/test/support/http_handling_shared.rb +11 -0
  134. data/test/support/proxy_server.rb +207 -0
  135. data/test/support/servers/runner.rb +67 -0
  136. data/{spec → test}/support/simplecov.rb +11 -2
  137. data/test/support/ssl_helper.rb +108 -0
  138. data/test/test_helper.rb +38 -0
  139. metadata +108 -168
  140. data/.github/workflows/ci.yml +0 -67
  141. data/.gitignore +0 -15
  142. data/.rspec +0 -1
  143. data/.rubocop/layout.yml +0 -8
  144. data/.rubocop/metrics.yml +0 -4
  145. data/.rubocop/rspec.yml +0 -9
  146. data/.rubocop/style.yml +0 -32
  147. data/.rubocop.yml +0 -11
  148. data/.rubocop_todo.yml +0 -219
  149. data/.yardopts +0 -2
  150. data/CHANGES_OLD.md +0 -1002
  151. data/Gemfile +0 -51
  152. data/Guardfile +0 -18
  153. data/Rakefile +0 -64
  154. data/lib/http/headers/mixin.rb +0 -34
  155. data/lib/http/retriable/client.rb +0 -37
  156. data/logo.png +0 -0
  157. data/spec/lib/http/client_spec.rb +0 -556
  158. data/spec/lib/http/connection_spec.rb +0 -88
  159. data/spec/lib/http/content_type_spec.rb +0 -47
  160. data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
  161. data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
  162. data/spec/lib/http/features/instrumentation_spec.rb +0 -81
  163. data/spec/lib/http/features/logging_spec.rb +0 -65
  164. data/spec/lib/http/features/raise_error_spec.rb +0 -62
  165. data/spec/lib/http/headers/mixin_spec.rb +0 -36
  166. data/spec/lib/http/headers/normalizer_spec.rb +0 -52
  167. data/spec/lib/http/headers_spec.rb +0 -527
  168. data/spec/lib/http/options/body_spec.rb +0 -15
  169. data/spec/lib/http/options/features_spec.rb +0 -33
  170. data/spec/lib/http/options/form_spec.rb +0 -15
  171. data/spec/lib/http/options/headers_spec.rb +0 -24
  172. data/spec/lib/http/options/json_spec.rb +0 -15
  173. data/spec/lib/http/options/merge_spec.rb +0 -68
  174. data/spec/lib/http/options/new_spec.rb +0 -30
  175. data/spec/lib/http/options/proxy_spec.rb +0 -20
  176. data/spec/lib/http/options_spec.rb +0 -13
  177. data/spec/lib/http/redirector_spec.rb +0 -530
  178. data/spec/lib/http/request/body_spec.rb +0 -211
  179. data/spec/lib/http/request/writer_spec.rb +0 -121
  180. data/spec/lib/http/request_spec.rb +0 -234
  181. data/spec/lib/http/response/body_spec.rb +0 -85
  182. data/spec/lib/http/response/parser_spec.rb +0 -74
  183. data/spec/lib/http/response/status_spec.rb +0 -253
  184. data/spec/lib/http/response_spec.rb +0 -262
  185. data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
  186. data/spec/lib/http/retriable/performer_spec.rb +0 -302
  187. data/spec/lib/http/uri/normalizer_spec.rb +0 -95
  188. data/spec/lib/http/uri_spec.rb +0 -71
  189. data/spec/lib/http_spec.rb +0 -535
  190. data/spec/regression_specs.rb +0 -24
  191. data/spec/spec_helper.rb +0 -89
  192. data/spec/support/black_hole.rb +0 -13
  193. data/spec/support/dummy_server/servlet.rb +0 -203
  194. data/spec/support/dummy_server.rb +0 -44
  195. data/spec/support/fuubar.rb +0 -21
  196. data/spec/support/http_handling_shared.rb +0 -190
  197. data/spec/support/proxy_server.rb +0 -39
  198. data/spec/support/servers/config.rb +0 -11
  199. data/spec/support/servers/runner.rb +0 -19
  200. data/spec/support/ssl_helper.rb +0 -104
  201. /data/{spec → test}/support/capture_warning.rb +0 -0
@@ -0,0 +1,1140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "addressable/uri"
5
+
6
+ class HTTPURITest < Minitest::Test
7
+ cover "HTTP::URI*"
8
+
9
+ def example_ipv6_address
10
+ "2606:2800:220:1:248:1893:25c8:1946"
11
+ end
12
+
13
+ def example_http_uri_string
14
+ "http://example.com"
15
+ end
16
+
17
+ def example_https_uri_string
18
+ "https://example.com"
19
+ end
20
+
21
+ def example_ipv6_uri_string
22
+ "https://[#{example_ipv6_address}]"
23
+ end
24
+
25
+ def http_uri
26
+ @http_uri ||= HTTP::URI.parse(example_http_uri_string)
27
+ end
28
+
29
+ def https_uri
30
+ @https_uri ||= HTTP::URI.parse(example_https_uri_string)
31
+ end
32
+
33
+ def ipv6_uri
34
+ @ipv6_uri ||= HTTP::URI.parse(example_ipv6_uri_string)
35
+ end
36
+
37
+ def test_knows_uri_schemes
38
+ assert_equal "http", http_uri.scheme
39
+ assert_equal "https", https_uri.scheme
40
+ end
41
+
42
+ def test_sets_default_ports_for_http_uris
43
+ assert_equal 80, http_uri.port
44
+ end
45
+
46
+ def test_sets_default_ports_for_https_uris
47
+ assert_equal 443, https_uri.port
48
+ end
49
+
50
+ def test_host_strips_brackets_from_ipv6_addresses
51
+ assert_equal "2606:2800:220:1:248:1893:25c8:1946", ipv6_uri.host
52
+ end
53
+
54
+ def test_normalized_host_strips_brackets_from_ipv6_addresses
55
+ assert_equal "2606:2800:220:1:248:1893:25c8:1946", ipv6_uri.normalized_host
56
+ end
57
+
58
+ def test_inspect_returns_a_human_readable_representation
59
+ assert_match(%r{#<HTTP::URI:0x\h+ URI:http://example\.com>}, http_uri.inspect)
60
+ end
61
+
62
+ def test_host_assignment_updates_cached_values_for_host_and_normalized_host
63
+ uri = HTTP::URI.parse("http://example.com")
64
+
65
+ assert_equal "example.com", uri.host
66
+ assert_equal "example.com", uri.normalized_host
67
+
68
+ uri.host = "[#{example_ipv6_address}]"
69
+
70
+ assert_equal example_ipv6_address, uri.host
71
+ assert_equal example_ipv6_address, uri.normalized_host
72
+ end
73
+
74
+ def test_host_assignment_ensures_ipv6_addresses_are_bracketed_in_the_raw_host
75
+ uri = HTTP::URI.parse("http://example.com")
76
+
77
+ assert_equal "example.com", uri.host
78
+ assert_equal "example.com", uri.normalized_host
79
+
80
+ uri.host = example_ipv6_address
81
+
82
+ assert_equal example_ipv6_address, uri.host
83
+ assert_equal example_ipv6_address, uri.normalized_host
84
+ assert_equal "[#{example_ipv6_address}]", uri.instance_variable_get(:@raw_host)
85
+ end
86
+
87
+ def test_form_encode_encodes_key_value_pairs
88
+ assert_equal "foo=bar&baz=quux", HTTP::URI.form_encode({ foo: "bar", baz: "quux" })
89
+ end
90
+
91
+ def test_initialize_raises_argument_error_for_positional_argument
92
+ assert_raises(ArgumentError) { HTTP::URI.new(42) }
93
+ end
94
+
95
+ def test_http_predicate_returns_true_for_http_uris
96
+ assert_predicate http_uri, :http?
97
+ end
98
+
99
+ def test_http_predicate_returns_false_for_https_uris
100
+ refute_predicate https_uri, :http?
101
+ end
102
+
103
+ def test_eql_returns_true_for_equivalent_uris
104
+ assert http_uri.eql?(HTTP::URI.parse(example_http_uri_string))
105
+ end
106
+
107
+ def test_eql_returns_false_for_non_uri_objects
108
+ refute http_uri.eql?("http://example.com")
109
+ end
110
+
111
+ def test_hash_returns_an_integer
112
+ assert_kind_of Integer, http_uri.hash
113
+ end
114
+
115
+ def test_dup_doesnt_share_internal_value_between_duplicates
116
+ uri = HTTP::URI.parse("http://example.com")
117
+ duplicated_uri = uri.dup
118
+ duplicated_uri.host = "example.org"
119
+
120
+ assert_equal "http://example.org", duplicated_uri.to_s
121
+ assert_equal "http://example.com", uri.to_s
122
+ end
123
+
124
+ def test_dup_returns_an_http_uri_instance
125
+ assert_instance_of HTTP::URI, http_uri.dup
126
+ end
127
+
128
+ def test_dup_preserves_all_uri_components
129
+ uri = HTTP::URI.parse("http://user:pass@example.com:8080/path?q=1#frag")
130
+ duped = uri.dup
131
+
132
+ assert_equal "http", duped.scheme
133
+ assert_equal "user", duped.user
134
+ assert_equal "pass", duped.password
135
+ assert_equal "example.com", duped.host
136
+ assert_equal 8080, duped.port
137
+ assert_equal "/path", duped.path
138
+ assert_equal "q=1", duped.query
139
+ assert_equal "frag", duped.fragment
140
+ end
141
+
142
+ def test_dup_preserves_ipv6_host_with_brackets
143
+ duped = ipv6_uri.dup
144
+
145
+ assert_equal example_ipv6_address, duped.host
146
+ assert_equal "https://[#{example_ipv6_address}]", duped.to_s
147
+ end
148
+
149
+ def test_parse_returns_the_same_object_when_given_an_http_uri
150
+ assert_same http_uri, HTTP::URI.parse(http_uri)
151
+ end
152
+
153
+ def test_parse_returns_a_new_http_uri_when_given_a_string
154
+ result = HTTP::URI.parse("http://example.com")
155
+
156
+ assert_instance_of HTTP::URI, result
157
+ end
158
+
159
+ def test_parse_returns_the_same_object_when_given_a_uri_subclass_instance
160
+ subclass = Class.new(HTTP::URI)
161
+ sub_uri = subclass.new(scheme: "http", host: "example.com")
162
+ # is_a?(self) returns true for subclasses; instance_of? does not
163
+ assert_same sub_uri, HTTP::URI.parse(sub_uri)
164
+ end
165
+
166
+ def test_parse_raises_invalid_error_for_nil
167
+ err = assert_raises(HTTP::URI::InvalidError) do
168
+ HTTP::URI.parse(nil)
169
+ end
170
+ assert_equal "invalid URI: nil", err.message
171
+ end
172
+
173
+ def test_parse_raises_invalid_error_for_malformed_uri
174
+ err = assert_raises(HTTP::URI::InvalidError) do
175
+ HTTP::URI.parse(":")
176
+ end
177
+ assert_equal 'invalid URI: ":"', err.message
178
+ end
179
+
180
+ def test_form_encode_sorts_key_value_pairs_when_sort_is_true
181
+ unsorted = HTTP::URI.form_encode([[:z, 1], [:a, 2]])
182
+ sorted = HTTP::URI.form_encode([[:z, 1], [:a, 2]], sort: true)
183
+
184
+ assert_equal "z=1&a=2", unsorted
185
+ assert_equal "a=2&z=1", sorted
186
+ end
187
+
188
+ def test_form_encode_encodes_newlines_as_percent_0a
189
+ assert_equal "text=hello%0Aworld", HTTP::URI.form_encode({ text: "hello\nworld" })
190
+ end
191
+
192
+ def test_form_encode_sorts_by_string_representation_of_keys
193
+ result = HTTP::URI.form_encode([[2, "b"], [10, "a"]], sort: true)
194
+
195
+ assert_equal "10=a&2=b", result
196
+ end
197
+
198
+ def test_percent_encode_returns_nil_when_given_nil
199
+ assert_nil HTTP::URI.send(:percent_encode, nil)
200
+ end
201
+
202
+ def test_percent_encode_returns_the_same_string_when_no_encoding_is_needed
203
+ assert_equal "hello", HTTP::URI.send(:percent_encode, "hello")
204
+ end
205
+
206
+ def test_percent_encode_encodes_non_ascii_characters_as_percent_encoded_utf8_bytes
207
+ assert_equal "h%C3%A9llo", HTTP::URI.send(:percent_encode, "héllo")
208
+ end
209
+
210
+ def test_percent_encode_encodes_multi_byte_characters_into_multiple_percent_encoded_sequences
211
+ # U+1F600 (grinning face) is 4 bytes in UTF-8: F0 9F 98 80
212
+ result = HTTP::URI.send(:percent_encode, "\u{1F600}")
213
+
214
+ assert_equal "%F0%9F%98%80", result
215
+ end
216
+
217
+ def test_percent_encode_encodes_spaces_as_percent_20
218
+ assert_equal "hello%20world", HTTP::URI.send(:percent_encode, "hello world")
219
+ end
220
+
221
+ def test_percent_encode_does_not_encode_printable_ascii_characters
222
+ printable = (0x21..0x7E).map(&:chr).join
223
+
224
+ assert_equal printable, HTTP::URI.send(:percent_encode, printable)
225
+ end
226
+
227
+ def test_percent_encode_uses_uppercase_hex_digits
228
+ result = HTTP::URI.send(:percent_encode, "\xFF".b.encode(Encoding::UTF_8, Encoding::ISO_8859_1))
229
+
230
+ assert_equal "%C3%BF", result
231
+ end
232
+
233
+ def remove_dot_segments(path)
234
+ HTTP::URI.send(:remove_dot_segments, path)
235
+ end
236
+
237
+ def test_remove_dot_segments_resolves_parent_directory_references
238
+ assert_equal "/a/c", remove_dot_segments("/a/b/../c")
239
+ end
240
+
241
+ def test_remove_dot_segments_removes_current_directory_references
242
+ assert_equal "/a/b/c", remove_dot_segments("/a/./b/c")
243
+ end
244
+
245
+ def test_remove_dot_segments_resolves_multiple_parent_references
246
+ assert_equal "/c", remove_dot_segments("/a/b/../../c")
247
+ end
248
+
249
+ def test_remove_dot_segments_clamps_parent_references_above_root
250
+ assert_equal "/a", remove_dot_segments("/../a")
251
+ end
252
+
253
+ def test_remove_dot_segments_preserves_paths_without_dot_segments
254
+ assert_equal "/a/b/c", remove_dot_segments("/a/b/c")
255
+ end
256
+
257
+ def test_remove_dot_segments_preserves_trailing_slash_after_parent_reference
258
+ assert_equal "/a/", remove_dot_segments("/a/b/..")
259
+ end
260
+
261
+ def test_remove_dot_segments_resolves_current_directory_at_end_of_path
262
+ assert_equal "/a/b/", remove_dot_segments("/a/b/.")
263
+ end
264
+
265
+ def test_remove_dot_segments_handles_standalone_dot
266
+ assert_equal "", remove_dot_segments(".")
267
+ end
268
+
269
+ def test_remove_dot_segments_handles_standalone_dot_dot
270
+ assert_equal "", remove_dot_segments("..")
271
+ end
272
+
273
+ def test_remove_dot_segments_handles_leading_dot_slash_prefix
274
+ assert_equal "a", remove_dot_segments("./a")
275
+ end
276
+
277
+ def test_remove_dot_segments_handles_leading_dot_dot_slash_prefix
278
+ assert_equal "a", remove_dot_segments("../a")
279
+ end
280
+
281
+ def test_remove_dot_segments_handles_empty_path
282
+ assert_equal "", remove_dot_segments("")
283
+ end
284
+
285
+ def test_remove_dot_segments_pops_empty_segment_when_dot_dot_follows_double_slash
286
+ assert_equal "/", remove_dot_segments("//..")
287
+ end
288
+
289
+ def test_normalizer_normalizes_an_empty_path_to_slash
290
+ result = HTTP::URI::NORMALIZER.call("http://example.com")
291
+
292
+ assert_equal "/", result.path
293
+ end
294
+
295
+ def test_normalizer_preserves_non_empty_paths
296
+ result = HTTP::URI::NORMALIZER.call("http://example.com/foo/bar")
297
+
298
+ assert_equal "/foo/bar", result.path
299
+ end
300
+
301
+ def test_normalizer_removes_dot_segments_from_paths
302
+ result = HTTP::URI::NORMALIZER.call("http://example.com/a/b/../c")
303
+
304
+ assert_equal "/a/c", result.path
305
+ end
306
+
307
+ def test_normalizer_percent_encodes_non_ascii_characters_in_paths
308
+ result = HTTP::URI::NORMALIZER.call("http://example.com/p\u00E4th")
309
+
310
+ assert_includes result.path, "%"
311
+ end
312
+
313
+ def test_normalizer_percent_encodes_non_ascii_characters_in_query_strings
314
+ result = HTTP::URI::NORMALIZER.call("http://example.com/?q=v\u00E4lue")
315
+
316
+ assert_includes result.query, "%"
317
+ end
318
+
319
+ def test_normalizer_returns_an_http_uri_instance
320
+ assert_instance_of HTTP::URI, HTTP::URI::NORMALIZER.call("http://example.com/path")
321
+ end
322
+
323
+ def test_normalizer_lowercases_the_scheme
324
+ result = HTTP::URI::NORMALIZER.call("HTTP://example.com")
325
+
326
+ assert_equal "http", result.scheme
327
+ end
328
+
329
+ def test_normalizer_lowercases_the_host
330
+ result = HTTP::URI::NORMALIZER.call("http://EXAMPLE.COM")
331
+
332
+ assert_equal "example.com", result.host
333
+ end
334
+
335
+ def test_normalizer_omits_default_http_port
336
+ result = HTTP::URI::NORMALIZER.call("http://example.com:80/path")
337
+
338
+ assert_equal "http://example.com/path", result.to_s
339
+ end
340
+
341
+ def test_normalizer_omits_default_https_port
342
+ result = HTTP::URI::NORMALIZER.call("https://example.com:443/path")
343
+
344
+ assert_equal "https://example.com/path", result.to_s
345
+ end
346
+
347
+ def test_normalizer_preserves_non_default_port
348
+ result = HTTP::URI::NORMALIZER.call("http://example.com:8080/path")
349
+
350
+ assert_equal "http://example.com:8080/path", result.to_s
351
+ end
352
+
353
+ def test_normalizer_preserves_ipv6_host
354
+ result = HTTP::URI::NORMALIZER.call("http://[::1]:8080/path")
355
+
356
+ assert_equal "http://[::1]:8080/path", result.to_s
357
+ end
358
+
359
+ def test_normalizer_preserves_user_info
360
+ result = HTTP::URI::NORMALIZER.call("http://user:pass@example.com/path")
361
+
362
+ assert_equal "user", result.user
363
+ assert_equal "pass", result.password
364
+ end
365
+
366
+ def test_normalizer_preserves_fragment
367
+ result = HTTP::URI::NORMALIZER.call("http://example.com/path#frag")
368
+
369
+ assert_equal "frag", result.fragment
370
+ end
371
+
372
+ def test_equality_returns_false_when_compared_to_a_non_uri_object
373
+ refute_equal "http://example.com", http_uri
374
+ end
375
+
376
+ def test_equality_returns_true_for_uris_that_normalize_to_the_same_form
377
+ uri1 = HTTP::URI.parse("HTTP://EXAMPLE.COM")
378
+ uri2 = HTTP::URI.parse("http://example.com")
379
+
380
+ assert_equal uri1, uri2
381
+ end
382
+
383
+ def test_equality_returns_false_for_uris_that_normalize_differently
384
+ uri1 = HTTP::URI.parse("http://example.com/a")
385
+ uri2 = HTTP::URI.parse("http://example.com/b")
386
+
387
+ refute_equal uri1, uri2
388
+ end
389
+
390
+ def test_equality_returns_true_when_compared_to_a_uri_subclass_instance
391
+ subclass = Class.new(HTTP::URI)
392
+ sub_uri = subclass.new(scheme: "http", host: "example.com")
393
+
394
+ assert_equal http_uri, sub_uri
395
+ end
396
+
397
+ def test_eql_returns_false_for_uris_with_different_string_representations
398
+ uri1 = HTTP::URI.parse("http://example.com")
399
+ uri2 = HTTP::URI.parse("http://example.com/")
400
+
401
+ refute uri1.eql?(uri2)
402
+ end
403
+
404
+ def test_eql_returns_true_for_a_uri_subclass_instance_with_same_string
405
+ subclass = Class.new(HTTP::URI)
406
+ sub_uri = subclass.new(scheme: "http", host: "example.com")
407
+
408
+ assert http_uri.eql?(sub_uri)
409
+ end
410
+
411
+ def test_hash_returns_the_same_value_on_repeated_calls
412
+ uri = HTTP::URI.parse("http://example.com")
413
+ first = uri.hash
414
+ second = uri.hash
415
+
416
+ assert_equal first, second
417
+ end
418
+
419
+ def test_hash_returns_a_composite_hash_of_class_and_string_representation
420
+ uri = HTTP::URI.parse("http://example.com")
421
+
422
+ assert_equal [HTTP::URI, uri.to_s].hash, uri.hash
423
+ end
424
+
425
+ def test_port_returns_the_explicit_port_when_one_is_set
426
+ uri = HTTP::URI.parse("http://example.com:8080")
427
+
428
+ assert_equal 8080, uri.port
429
+ end
430
+
431
+ def test_origin_returns_scheme_and_host_for_http_uris
432
+ assert_equal "http://example.com", http_uri.origin
433
+ end
434
+
435
+ def test_origin_returns_scheme_and_host_for_https_uris
436
+ assert_equal "https://example.com", https_uri.origin
437
+ end
438
+
439
+ def test_origin_includes_non_default_port
440
+ uri = HTTP::URI.parse("http://example.com:8080")
441
+
442
+ assert_equal "http://example.com:8080", uri.origin
443
+ end
444
+
445
+ def test_origin_omits_default_http_port
446
+ uri = HTTP::URI.parse("http://example.com:80")
447
+
448
+ assert_equal "http://example.com", uri.origin
449
+ end
450
+
451
+ def test_origin_omits_default_https_port
452
+ uri = HTTP::URI.parse("https://example.com:443")
453
+
454
+ assert_equal "https://example.com", uri.origin
455
+ end
456
+
457
+ def test_origin_normalizes_scheme_to_lowercase
458
+ uri = HTTP::URI.parse("HTTP://example.com")
459
+
460
+ assert_equal "http://example.com", uri.origin
461
+ end
462
+
463
+ def test_origin_normalizes_host_to_lowercase
464
+ uri = HTTP::URI.parse("http://EXAMPLE.COM")
465
+
466
+ assert_equal "http://example.com", uri.origin
467
+ end
468
+
469
+ def test_origin_preserves_ipv6_brackets
470
+ assert_equal "https://[2606:2800:220:1:248:1893:25c8:1946]", ipv6_uri.origin
471
+ end
472
+
473
+ def test_origin_excludes_user_info
474
+ uri = HTTP::URI.parse("http://user:pass@example.com")
475
+
476
+ assert_equal "http://example.com", uri.origin
477
+ end
478
+
479
+ def test_origin_handles_uri_with_no_scheme
480
+ uri = HTTP::URI.new(host: "example.com")
481
+
482
+ assert_equal "://example.com", uri.origin
483
+ end
484
+
485
+ def test_origin_handles_uri_with_no_host
486
+ uri = HTTP::URI.new(path: "/foo")
487
+
488
+ assert_equal "://", uri.origin
489
+ end
490
+
491
+ def test_request_uri_returns_path_for_a_simple_uri
492
+ uri = HTTP::URI.parse("http://example.com/path")
493
+
494
+ assert_equal "/path", uri.request_uri
495
+ end
496
+
497
+ def test_request_uri_returns_path_and_query
498
+ uri = HTTP::URI.parse("http://example.com/path?q=1")
499
+
500
+ assert_equal "/path?q=1", uri.request_uri
501
+ end
502
+
503
+ def test_request_uri_returns_slash_for_empty_path
504
+ assert_equal "/", http_uri.request_uri
505
+ end
506
+
507
+ def test_request_uri_returns_slash_with_query_for_empty_path
508
+ uri = HTTP::URI.parse("http://example.com?q=1")
509
+
510
+ assert_equal "/?q=1", uri.request_uri
511
+ end
512
+
513
+ def test_request_uri_preserves_trailing_question_mark_with_empty_query
514
+ uri = HTTP::URI.parse("http://example.com/path?")
515
+
516
+ assert_equal "/path?", uri.request_uri
517
+ end
518
+
519
+ def test_omit_returns_an_http_uri_instance
520
+ full_uri = HTTP::URI.parse("http://user:pass@example.com:8080/path?q=1#frag")
521
+
522
+ assert_instance_of HTTP::URI, full_uri.omit(:fragment)
523
+ end
524
+
525
+ def test_omit_removes_the_fragment_component
526
+ full_uri = HTTP::URI.parse("http://user:pass@example.com:8080/path?q=1#frag")
527
+
528
+ assert_nil full_uri.omit(:fragment).fragment
529
+ end
530
+
531
+ def test_omit_removes_multiple_components
532
+ full_uri = HTTP::URI.parse("http://user:pass@example.com:8080/path?q=1#frag")
533
+ result = full_uri.omit(:query, :fragment)
534
+
535
+ assert_nil result.query
536
+ assert_nil result.fragment
537
+ end
538
+
539
+ def test_omit_preserves_all_other_components_when_omitting_fragment
540
+ full_uri = HTTP::URI.parse("http://user:pass@example.com:8080/path?q=1#frag")
541
+ result = full_uri.omit(:fragment)
542
+
543
+ assert_equal "http", result.scheme
544
+ assert_equal "user", result.user
545
+ assert_equal "pass", result.password
546
+ assert_equal "example.com", result.host
547
+ assert_equal 8080, result.port
548
+ assert_equal "/path", result.path
549
+ assert_equal "q=1", result.query
550
+ end
551
+
552
+ def test_omit_does_not_add_default_port_when_omitting_components
553
+ uri = HTTP::URI.parse("http://example.com/path#frag")
554
+
555
+ assert_equal "http://example.com/path", uri.omit(:fragment).to_s
556
+ end
557
+
558
+ def test_omit_preserves_ipv6_host_when_omitting_components
559
+ uri = HTTP::URI.parse("https://[::1]:8080/path#frag")
560
+
561
+ assert_equal "https://[::1]:8080/path", uri.omit(:fragment).to_s
562
+ end
563
+
564
+ def test_omit_returns_unchanged_uri_when_no_components_given
565
+ full_uri = HTTP::URI.parse("http://user:pass@example.com:8080/path?q=1#frag")
566
+
567
+ assert_equal full_uri.to_s, full_uri.omit.to_s
568
+ end
569
+
570
+ def test_join_resolves_a_relative_path
571
+ result = HTTP::URI.parse("http://example.com/foo/").join("bar")
572
+
573
+ assert_equal "http://example.com/foo/bar", result.to_s
574
+ end
575
+
576
+ def test_join_resolves_an_absolute_path
577
+ result = HTTP::URI.parse("http://example.com/foo").join("/bar")
578
+
579
+ assert_equal "http://example.com/bar", result.to_s
580
+ end
581
+
582
+ def test_join_resolves_a_full_uri
583
+ result = HTTP::URI.parse("http://example.com/foo").join("http://other.com/bar")
584
+
585
+ assert_equal "http://other.com/bar", result.to_s
586
+ end
587
+
588
+ def test_join_returns_an_http_uri_instance
589
+ result = HTTP::URI.parse("http://example.com/foo/").join("bar")
590
+
591
+ assert_instance_of HTTP::URI, result
592
+ end
593
+
594
+ def test_join_accepts_an_http_uri_as_argument
595
+ other = HTTP::URI.parse("http://other.com/bar")
596
+ result = HTTP::URI.parse("http://example.com/foo").join(other)
597
+
598
+ assert_equal "http://other.com/bar", result.to_s
599
+ end
600
+
601
+ def test_join_percent_encodes_non_ascii_characters_in_the_base_uri
602
+ result = HTTP::URI.parse("http://example.com/K\u00F6nig/").join("bar")
603
+
604
+ assert_equal "http://example.com/K%C3%B6nig/bar", result.to_s
605
+ end
606
+
607
+ def test_join_percent_encodes_non_ascii_characters_in_the_other_uri
608
+ result = HTTP::URI.parse("http://example.com/").join("/K\u00F6nig")
609
+
610
+ assert_equal "http://example.com/K%C3%B6nig", result.to_s
611
+ end
612
+
613
+ def test_http_predicate_returns_false_for_non_http_https_schemes
614
+ uri = HTTP::URI.parse("ftp://example.com")
615
+
616
+ refute_predicate uri, :http?
617
+ end
618
+
619
+ def test_https_predicate_returns_true_for_https_uris
620
+ assert_predicate https_uri, :https?
621
+ end
622
+
623
+ def test_https_predicate_returns_false_for_http_uris
624
+ refute_predicate http_uri, :https?
625
+ end
626
+
627
+ def test_https_predicate_returns_false_for_non_http_https_schemes
628
+ uri = HTTP::URI.parse("ftp://example.com")
629
+
630
+ refute_predicate uri, :https?
631
+ end
632
+
633
+ def test_to_s_returns_the_string_representation
634
+ assert_equal "http://example.com", http_uri.to_s
635
+ end
636
+
637
+ def test_to_str_is_aliased_to_to_s
638
+ assert_equal http_uri.to_s, http_uri.to_str
639
+ end
640
+
641
+ def test_inspect_includes_the_class_name
642
+ assert_includes http_uri.inspect, "HTTP::URI"
643
+ end
644
+
645
+ def test_inspect_includes_the_uri_string
646
+ assert_includes http_uri.inspect, "URI:http://example.com"
647
+ end
648
+
649
+ def test_inspect_formats_the_object_id_correctly_with_shift
650
+ expected_hex = format("%014x", http_uri.object_id << 1)
651
+
652
+ assert_includes http_uri.inspect, expected_hex
653
+ end
654
+
655
+ def test_initialize_accepts_keyword_arguments
656
+ uri = HTTP::URI.new(scheme: "http", host: "example.com")
657
+
658
+ assert_equal "http", uri.scheme
659
+ assert_equal "example.com", uri.host
660
+ end
661
+
662
+ def test_initialize_raises_argument_error_for_an_addressable_uri
663
+ addr_uri = Addressable::URI.parse("http://example.com")
664
+
665
+ assert_raises(ArgumentError) { HTTP::URI.new(addr_uri) }
666
+ end
667
+
668
+ def test_initialize_raises_argument_error_for_a_positional_argument
669
+ assert_raises(ArgumentError) { HTTP::URI.new(42) }
670
+ end
671
+
672
+ def test_initialize_works_with_no_arguments
673
+ uri = HTTP::URI.new
674
+
675
+ assert_instance_of HTTP::URI, uri
676
+ end
677
+
678
+ def test_deconstruct_keys_returns_all_keys_when_given_nil
679
+ full_uri = HTTP::URI.parse("http://user:pass@example.com:8080/path?q=1#frag")
680
+ result = full_uri.deconstruct_keys(nil)
681
+
682
+ assert_equal "http", result[:scheme]
683
+ assert_equal "example.com", result[:host]
684
+ assert_equal 8080, result[:port]
685
+ assert_equal "/path", result[:path]
686
+ assert_equal "q=1", result[:query]
687
+ assert_equal "frag", result[:fragment]
688
+ assert_equal "user", result[:user]
689
+ assert_equal "pass", result[:password]
690
+ end
691
+
692
+ def test_deconstruct_keys_returns_only_requested_keys
693
+ result = http_uri.deconstruct_keys(%i[scheme host])
694
+
695
+ assert_equal({ scheme: "http", host: "example.com" }, result)
696
+ end
697
+
698
+ def test_deconstruct_keys_excludes_unrequested_keys
699
+ result = http_uri.deconstruct_keys([:host])
700
+
701
+ refute_includes result.keys, :scheme
702
+ refute_includes result.keys, :port
703
+ end
704
+
705
+ def test_deconstruct_keys_returns_empty_hash_for_empty_keys
706
+ assert_equal({}, http_uri.deconstruct_keys([]))
707
+ end
708
+
709
+ def test_deconstruct_keys_returns_correct_port_for_https_uris
710
+ assert_equal 443, https_uri.deconstruct_keys([:port])[:port]
711
+ end
712
+
713
+ def test_deconstruct_keys_supports_pattern_matching_with_case_in
714
+ matched = case http_uri
715
+ in { scheme: "http", host: /example/ }
716
+ true
717
+ else
718
+ false
719
+ end
720
+
721
+ assert matched
722
+ end
723
+
724
+ def test_process_ipv6_brackets_handles_ipv4_addresses
725
+ uri = HTTP::URI.parse("http://example.com")
726
+ uri.host = "192.168.1.1"
727
+
728
+ assert_equal "192.168.1.1", uri.host
729
+ end
730
+
731
+ def test_process_ipv6_brackets_handles_regular_hostnames
732
+ uri = HTTP::URI.parse("http://example.com")
733
+ uri.host = "example.org"
734
+
735
+ assert_equal "example.org", uri.host
736
+ end
737
+
738
+ def test_process_ipv6_brackets_handles_invalid_ip_address_strings_gracefully
739
+ uri = HTTP::URI.parse("http://example.com")
740
+ uri.host = "not-an-ip"
741
+
742
+ assert_equal "not-an-ip", uri.host
743
+ end
744
+
745
+ def test_parse_raises_invalid_error_with_message_containing_inspect_for_non_stringable_objects
746
+ obj = Object.new
747
+ def obj.to_str
748
+ raise NoMethodError
749
+ end
750
+
751
+ err = assert_raises(HTTP::URI::InvalidError) do
752
+ HTTP::URI.parse(obj)
753
+ end
754
+ assert_kind_of HTTP::URI::InvalidError, err
755
+ assert_includes err.message, "invalid URI: "
756
+ assert_includes err.message, obj.inspect
757
+ refute_equal obj.inspect, err.message
758
+ end
759
+
760
+ def test_parse_raises_invalid_error_for_an_object_whose_to_str_raises_type_error
761
+ obj = Object.new
762
+ def obj.to_str
763
+ raise TypeError
764
+ end
765
+
766
+ err = assert_raises(HTTP::URI::InvalidError) do
767
+ HTTP::URI.parse(obj)
768
+ end
769
+ assert_kind_of HTTP::URI::InvalidError, err
770
+ assert_includes err.message, obj.inspect
771
+ refute_equal obj.inspect, err.message
772
+ end
773
+
774
+ def test_parse_via_parse_components_parses_non_ascii_uri_with_all_components
775
+ uri = HTTP::URI.parse("http://us\u00E9r:p\u00E4ss@ex\u00E4mple.com:9090/p\u00E4th?q=v\u00E4l#fr\u00E4g")
776
+
777
+ assert_equal "us\u00E9r", uri.user
778
+ assert_equal "p\u00E4ss", uri.password
779
+ assert_equal 9090, uri.port
780
+ assert_includes String(uri), "fr\u00E4g"
781
+ end
782
+
783
+ def test_parse_via_parse_components_parses_an_ascii_uri_via_stdlib
784
+ uri = HTTP::URI.parse("http://example.com/path?q=1#frag")
785
+
786
+ assert_equal "http", uri.scheme
787
+ assert_equal "example.com", uri.host
788
+ assert_equal "/path", uri.path
789
+ assert_equal "q=1", uri.query
790
+ assert_equal "frag", uri.fragment
791
+ end
792
+
793
+ def test_parse_via_parse_components_strips_default_port_when_parsing_ascii_uri
794
+ uri = HTTP::URI.parse("http://example.com:80/path")
795
+
796
+ assert_equal "http://example.com/path", uri.to_s
797
+ end
798
+
799
+ def test_parse_via_parse_components_falls_back_to_addressable_when_stdlib_fails
800
+ uri = HTTP::URI.parse("http://example.com/path with spaces")
801
+
802
+ assert_equal "http", uri.scheme
803
+ assert_equal "example.com", uri.host
804
+ end
805
+
806
+ def test_parse_via_parse_components_raises_invalid_error_for_invalid_non_ascii_uri
807
+ err = assert_raises(HTTP::URI::InvalidError) do
808
+ HTTP::URI.parse("ht\u00FCtp://[invalid")
809
+ end
810
+ assert_kind_of HTTP::URI::InvalidError, err
811
+ assert_includes err.message, "invalid URI:"
812
+ assert_includes err.message, "invalid"
813
+ end
814
+
815
+ def test_parse_via_parse_components_raises_invalid_error_for_stdlib_invalid_uri
816
+ err = assert_raises(HTTP::URI::InvalidError) do
817
+ HTTP::URI.parse("http://exam ple.com")
818
+ end
819
+ assert_kind_of HTTP::URI::InvalidError, err
820
+ assert_includes err.message, "invalid URI:"
821
+ assert_includes err.message, "exam ple.com"
822
+ end
823
+
824
+ def test_parse_via_parse_components_parses_non_ascii_uri_preserving_fragment
825
+ uri = HTTP::URI.parse("http://ex\u00E4mple.com/path#sec\u00F6tion")
826
+
827
+ assert_equal "sec\u00F6tion", uri.fragment
828
+ end
829
+
830
+ def test_parse_via_parse_components_parses_non_ascii_uri_preserving_user_without_password
831
+ uri = HTTP::URI.parse("http://\u00FCser@ex\u00E4mple.com/")
832
+
833
+ assert_equal "\u00FCser", uri.user
834
+ assert_nil uri.password
835
+ end
836
+
837
+ def test_parse_via_parse_components_routes_ascii_control_characters_to_addressable
838
+ uri = HTTP::URI.parse("http://example.com/?\x00\x7F\n")
839
+
840
+ assert_equal "\x00\x7F\n", uri.query
841
+ end
842
+
843
+ def test_to_s_serializes_scheme_only_uri
844
+ uri = HTTP::URI.new(scheme: "http")
845
+
846
+ assert_equal "http:", uri.to_s
847
+ end
848
+
849
+ def test_to_s_omits_scheme_prefix_when_scheme_is_nil
850
+ uri = HTTP::URI.new(host: "example.com", path: "/path")
851
+
852
+ assert_equal "//example.com/path", uri.to_s
853
+ end
854
+
855
+ def test_to_s_serializes_uri_with_user_and_password
856
+ uri = HTTP::URI.new(scheme: "http", user: "admin", password: "secret", host: "example.com")
857
+
858
+ assert_equal "http://admin:secret@example.com", uri.to_s
859
+ end
860
+
861
+ def test_to_s_serializes_uri_with_user_but_no_password
862
+ uri = HTTP::URI.new(scheme: "http", user: "admin", host: "example.com")
863
+
864
+ assert_equal "http://admin@example.com", uri.to_s
865
+ end
866
+
867
+ def test_to_s_serializes_uri_with_explicit_port
868
+ uri = HTTP::URI.new(scheme: "http", host: "example.com", port: 8080)
869
+
870
+ assert_equal "http://example.com:8080", uri.to_s
871
+ end
872
+
873
+ def test_to_s_serializes_uri_with_query
874
+ uri = HTTP::URI.new(scheme: "http", host: "example.com", path: "/path", query: "a=1")
875
+
876
+ assert_equal "http://example.com/path?a=1", uri.to_s
877
+ end
878
+
879
+ def test_to_s_serializes_uri_with_fragment
880
+ uri = HTTP::URI.new(scheme: "http", host: "example.com", path: "/path", fragment: "sec")
881
+
882
+ assert_equal "http://example.com/path#sec", uri.to_s
883
+ end
884
+
885
+ def test_to_s_serializes_uri_with_all_components
886
+ uri = HTTP::URI.new(
887
+ scheme: "http", user: "u", password: "p", host: "h.com",
888
+ port: 9090, path: "/x", query: "q=1", fragment: "f"
889
+ )
890
+
891
+ assert_equal "http://u:p@h.com:9090/x?q=1#f", uri.to_s
892
+ end
893
+
894
+ def test_to_s_serializes_path_only_uri
895
+ uri = HTTP::URI.new(path: "/just/a/path")
896
+
897
+ assert_equal "/just/a/path", uri.to_s
898
+ end
899
+
900
+ def test_to_s_serializes_uri_without_host_omitting_double_slash
901
+ uri = HTTP::URI.new(scheme: "mailto", path: "user@example.com")
902
+
903
+ assert_equal "mailto:user@example.com", uri.to_s
904
+ end
905
+
906
+ def test_to_s_serializes_query_only_uri_without_host
907
+ uri = HTTP::URI.new(path: "/p", query: "q=1")
908
+
909
+ assert_equal "/p?q=1", uri.to_s
910
+ end
911
+
912
+ def test_to_s_serializes_fragment_only_uri_without_host
913
+ uri = HTTP::URI.new(path: "/p", fragment: "f")
914
+
915
+ assert_equal "/p#f", uri.to_s
916
+ end
917
+
918
+ def test_normalize_lowercases_the_scheme
919
+ uri = HTTP::URI.new(scheme: "HTTP", host: "example.com")
920
+
921
+ assert_equal "http", uri.normalize.scheme
922
+ end
923
+
924
+ def test_normalize_lowercases_the_host
925
+ uri = HTTP::URI.new(scheme: "http", host: "EXAMPLE.COM")
926
+
927
+ assert_equal "example.com", uri.normalize.host
928
+ end
929
+
930
+ def test_normalize_strips_default_port
931
+ uri = HTTP::URI.new(scheme: "http", host: "example.com", port: 80, path: "/path")
932
+
933
+ assert_nil uri.normalize.instance_variable_get(:@port)
934
+ end
935
+
936
+ def test_normalize_preserves_non_default_port
937
+ uri = HTTP::URI.parse("http://example.com:8080/path")
938
+ normalized = uri.normalize
939
+
940
+ assert_equal 8080, normalized.instance_variable_get(:@port)
941
+ end
942
+
943
+ def test_normalize_normalizes_empty_path_to_slash_when_host_is_present
944
+ uri = HTTP::URI.new(scheme: "http", host: "example.com")
945
+
946
+ assert_equal "/", uri.normalize.path
947
+ end
948
+
949
+ def test_normalize_preserves_non_empty_path
950
+ uri = HTTP::URI.parse("http://example.com/foo")
951
+
952
+ assert_equal "/foo", uri.normalize.path
953
+ end
954
+
955
+ def test_normalize_preserves_user
956
+ uri = HTTP::URI.parse("http://myuser@example.com/")
957
+
958
+ assert_equal "myuser", uri.normalize.user
959
+ end
960
+
961
+ def test_normalize_preserves_password
962
+ uri = HTTP::URI.parse("http://u:mypass@example.com/")
963
+
964
+ assert_equal "mypass", uri.normalize.password
965
+ end
966
+
967
+ def test_normalize_preserves_query
968
+ uri = HTTP::URI.parse("http://example.com/?q=val")
969
+
970
+ assert_equal "q=val", uri.normalize.query
971
+ end
972
+
973
+ def test_normalize_preserves_fragment
974
+ uri = HTTP::URI.parse("http://example.com/#frag")
975
+
976
+ assert_equal "frag", uri.normalize.fragment
977
+ end
978
+
979
+ def test_normalize_handles_nil_scheme
980
+ uri = HTTP::URI.new(host: "example.com")
981
+
982
+ assert_nil uri.normalize.scheme
983
+ end
984
+
985
+ def test_normalize_handles_nil_host
986
+ uri = HTTP::URI.new(scheme: "http", path: "/path")
987
+
988
+ assert_nil uri.normalize.host
989
+ end
990
+
991
+ def test_normalize_does_not_normalize_empty_path_to_slash_without_host
992
+ uri = HTTP::URI.new(scheme: "http")
993
+
994
+ assert_equal "", uri.normalize.path
995
+ end
996
+
997
+ def test_normalize_returns_a_complete_normalized_string
998
+ uri = HTTP::URI.parse("HTTP://USER:PASS@EXAMPLE.COM:8080/path?q=1#frag")
999
+ normalized = uri.normalize
1000
+
1001
+ assert_equal "http://USER:PASS@example.com:8080/path?q=1#frag", String(normalized)
1002
+ end
1003
+
1004
+ def test_normalized_host_lowercases_the_host
1005
+ uri = HTTP::URI.new(host: "EXAMPLE.COM")
1006
+
1007
+ assert_equal "example.com", uri.normalized_host
1008
+ end
1009
+
1010
+ def test_normalized_host_decodes_percent_encoded_characters
1011
+ uri = HTTP::URI.new(host: "%65%78ample.com")
1012
+
1013
+ assert_equal "example.com", uri.normalized_host
1014
+ end
1015
+
1016
+ def test_normalized_host_decodes_multiple_percent_encoded_characters
1017
+ uri = HTTP::URI.new(host: "%65%78%61mple.com")
1018
+
1019
+ assert_equal "example.com", uri.normalized_host
1020
+ end
1021
+
1022
+ def test_normalized_host_strips_trailing_dot_from_domain
1023
+ uri = HTTP::URI.new(host: "example.com.")
1024
+
1025
+ assert_equal "example.com", uri.normalized_host
1026
+ end
1027
+
1028
+ def test_normalized_host_returns_nil_for_nil_host
1029
+ uri = HTTP::URI.new
1030
+
1031
+ assert_nil uri.normalized_host
1032
+ end
1033
+
1034
+ def test_normalized_host_encodes_idn_non_ascii_hostnames_to_ascii
1035
+ uri = HTTP::URI.new(host: "ex\u00E4mple.com")
1036
+
1037
+ assert_equal "xn--exmple-cua.com", uri.normalized_host
1038
+ end
1039
+
1040
+ def test_normalized_host_does_not_idn_encode_already_ascii_hostnames
1041
+ uri = HTTP::URI.new(host: "example.com")
1042
+
1043
+ assert_equal "example.com", uri.normalized_host
1044
+ end
1045
+
1046
+ def test_host_assignment_applies_normalize_host_to_the_new_host
1047
+ uri = HTTP::URI.parse("http://example.com")
1048
+ uri.host = "NEW-HOST.COM."
1049
+
1050
+ assert_equal "new-host.com", uri.normalized_host
1051
+ end
1052
+
1053
+ def test_default_port_returns_default_port_for_uppercase_scheme
1054
+ uri = HTTP::URI.new(scheme: "HTTP")
1055
+
1056
+ assert_equal 80, uri.default_port
1057
+ end
1058
+
1059
+ def test_default_port_returns_nil_for_unknown_scheme
1060
+ uri = HTTP::URI.new(scheme: "ftp")
1061
+
1062
+ assert_nil uri.default_port
1063
+ end
1064
+
1065
+ def test_default_port_returns_default_port_for_ws_scheme
1066
+ uri = HTTP::URI.new(scheme: "ws")
1067
+
1068
+ assert_equal 80, uri.default_port
1069
+ end
1070
+
1071
+ def test_default_port_returns_default_port_for_wss_scheme
1072
+ uri = HTTP::URI.new(scheme: "wss")
1073
+
1074
+ assert_equal 443, uri.default_port
1075
+ end
1076
+
1077
+ def test_origin_lowercases_an_uppercase_scheme
1078
+ uri = HTTP::URI.new(scheme: "HTTP", host: "example.com")
1079
+
1080
+ assert_equal "http://example.com", uri.origin
1081
+ end
1082
+
1083
+ def test_process_ipv6_brackets_returns_nil_host_as_nil
1084
+ uri = HTTP::URI.new(host: nil)
1085
+
1086
+ assert_nil uri.host
1087
+ end
1088
+
1089
+ def test_process_ipv6_brackets_does_not_strip_brackets_from_ipv4_addresses
1090
+ uri = HTTP::URI.new(host: "192.168.1.1")
1091
+
1092
+ assert_equal "192.168.1.1", uri.host
1093
+ assert_equal "192.168.1.1", uri.instance_variable_get(:@raw_host)
1094
+ end
1095
+
1096
+ def test_process_ipv6_brackets_does_not_bracket_ipv4_addresses_in_host_assignment
1097
+ uri = HTTP::URI.parse("http://example.com")
1098
+ uri.host = "10.0.0.1"
1099
+
1100
+ assert_equal "http://10.0.0.1", uri.to_s
1101
+ end
1102
+
1103
+ def test_parse_error_messages_uses_inspect_in_the_rescue
1104
+ obj = Object.new
1105
+ def obj.to_s
1106
+ "CUSTOM_TO_S"
1107
+ end
1108
+
1109
+ def obj.to_str
1110
+ raise NoMethodError
1111
+ end
1112
+
1113
+ err = assert_raises(HTTP::URI::InvalidError) do
1114
+ HTTP::URI.parse(obj)
1115
+ end
1116
+
1117
+ refute_includes err.message, "CUSTOM_TO_S"
1118
+ end
1119
+
1120
+ def test_dup_does_not_copy_memoized_hash_ivar
1121
+ uri = HTTP::URI.parse("http://example.com")
1122
+ uri.hash # memoize @hash
1123
+
1124
+ duped = uri.dup
1125
+
1126
+ refute duped.instance_variable_defined?(:@hash)
1127
+ end
1128
+
1129
+ def test_normalize_strips_port_443_for_https
1130
+ uri = HTTP::URI.new(scheme: "https", host: "example.com", port: 443, path: "/")
1131
+
1132
+ assert_nil uri.normalize.instance_variable_get(:@port)
1133
+ end
1134
+
1135
+ def test_normalize_does_not_strip_non_default_port
1136
+ uri = HTTP::URI.new(scheme: "http", host: "example.com", port: 9090, path: "/")
1137
+
1138
+ assert_equal 9090, uri.normalize.instance_variable_get(:@port)
1139
+ end
1140
+ end