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