Skip to content

Commit 4db79dc

Browse files
committed
Refactor code and tests for consistency and conciseness
1 parent f14a7a3 commit 4db79dc

10 files changed

Lines changed: 359 additions & 712 deletions

File tree

lib/http/form_data/composite_io.rb

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,9 @@ def readpartial(max_length)
9898

9999
return chunk if chunk && !chunk.empty?
100100

101-
advance_io
101+
@index += 1
102102
end
103103
end
104-
105-
# Advances cursor to the next IO object
106-
#
107-
# @api private
108-
# @return [void]
109-
def advance_io
110-
@index += 1
111-
end
112104
end
113105
end
114106
end

lib/http/form_data/file.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,10 @@ def close
8686
# @param [String, Pathname, IO] path_or_io
8787
# @return [IO]
8888
def make_io(path_or_io)
89-
if path_or_io.is_a?(String)
90-
::File.new(path_or_io, binmode: true)
91-
elsif path_or_io.is_a?(Pathname)
92-
path_or_io.open(binmode: true)
93-
else
94-
path_or_io
89+
case path_or_io
90+
when String then ::File.new(path_or_io, binmode: true)
91+
when Pathname then path_or_io.open(binmode: true)
92+
else path_or_io
9593
end
9694
end
9795

@@ -102,7 +100,7 @@ def make_io(path_or_io)
102100
# @return [String]
103101
def filename_for(io)
104102
if io.respond_to?(:path)
105-
io.path.split(::File::SEPARATOR).last
103+
::File.basename(io.path)
106104
else
107105
"stream-#{io.object_id}"
108106
end

lib/http/form_data/multipart.rb

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,9 @@ def tail
9797
# @api private
9898
# @return [Array<Param>]
9999
def parts(data)
100-
params = [] #: Array[Param]
101-
102-
FormData.ensure_data(data).each do |name, values|
103-
Array(values).each do |value|
104-
params << Param.new(name, value)
105-
end
100+
FormData.ensure_data(data).flat_map do |name, values|
101+
Array(values).map { |value| Param.new(name, value) }
106102
end
107-
108-
params
109103
end
110104
end
111105
end

lib/http/form_data/multipart/param.rb

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,8 @@ class Param
3131
# @return [Param]
3232
def initialize(name, value)
3333
@name = name.to_s
34-
35-
@part =
36-
if value.is_a?(Part)
37-
value
38-
else
39-
Part.new(value)
40-
end
41-
42-
@io = CompositeIO.new [header, @part, footer]
34+
@part = value.is_a?(Part) ? value : Part.new(value)
35+
@io = CompositeIO.new [header, @part, footer]
4336
end
4437

4538
private
@@ -61,9 +54,9 @@ def header
6154
# @return [String]
6255
def parameters
6356
fname = filename
64-
parameters = { name: @name }
65-
parameters[:filename] = fname if fname
66-
parameters.map { |k, v| "#{k}=#{v.inspect}" }.join("; ")
57+
params = { name: @name }
58+
params[:filename] = fname if fname
59+
params.map { |k, v| "#{k}=#{v.inspect}" }.join("; ")
6760
end
6861

6962
# Returns the content type of the wrapped part
@@ -87,7 +80,7 @@ def filename
8780
# @api private
8881
# @return [String]
8982
def footer
90-
"\r\n"
83+
CRLF
9184
end
9285
end
9386
end

test/http/form_data/composite_io_test.rb

Lines changed: 38 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,23 @@ def test_accepts_ios_and_strings
1717
assert_equal "Hello world!", io.read
1818
end
1919

20-
def test_fails_if_io_is_neither_string_nor_io
20+
def test_rejects_invalid_io_types
2121
error = assert_raises(ArgumentError) { HTTP::FormData::CompositeIO.new(%i[hello world]) }
2222

2323
assert_includes error.message, ":hello"
2424
assert_includes error.message, "is neither a String nor an IO object"
2525
end
2626

27+
def test_error_message_contains_inspect
28+
obj = Object.new
29+
def obj.inspect = "INVALID_IO_INSPECT"
30+
def obj.to_s = "INVALID_IO_TO_S"
31+
32+
error = assert_raises(ArgumentError) { HTTP::FormData::CompositeIO.new([obj]) }
33+
34+
assert_includes error.message, "INVALID_IO_INSPECT"
35+
end
36+
2737
def test_reads_all_data
2838
assert_equal "Hello world!", @composite_io.read
2939
end
@@ -35,13 +45,13 @@ def test_reads_partial_data
3545
assert_equal "world!", @composite_io.read(6)
3646
end
3747

38-
def test_returns_empty_string_when_no_data_retrieved
48+
def test_returns_empty_string_when_exhausted_without_length
3949
@composite_io.read
4050

4151
assert_equal "", @composite_io.read
4252
end
4353

44-
def test_returns_nil_when_no_partial_data_retrieved
54+
def test_returns_nil_when_exhausted_with_length
4555
@composite_io.read
4656

4757
assert_nil @composite_io.read(3)
@@ -72,15 +82,15 @@ def test_fills_buffer_with_retrieved_content
7282
assert_equal "world!", outbuf
7383
end
7484

75-
def test_returns_nil_when_no_partial_data_retrieved_with_buffer
85+
def test_clears_buffer_when_exhausted_with_length
7686
outbuf = +"content"
7787
@composite_io.read
7888

7989
assert_nil @composite_io.read(3, outbuf)
8090
assert_equal "", outbuf
8191
end
8292

83-
def test_returns_data_in_binary_encoding
93+
def test_returns_binary_encoding
8494
io = HTTP::FormData::CompositeIO.new(%w[Janko Marohnić])
8595

8696
assert_equal Encoding::BINARY, io.read(5).encoding
@@ -109,90 +119,62 @@ def test_rewinds_all_ios
109119
assert_equal "Hello world!", @composite_io.read
110120
end
111121

112-
def test_returns_sum_of_all_io_sizes
122+
def test_size_returns_sum_of_all_ios
113123
assert_equal 12, @composite_io.size
114124
end
115125

116-
def test_returns_zero_when_no_ios
117-
empty = HTTP::FormData::CompositeIO.new([])
118-
119-
assert_equal 0, empty.size
126+
def test_size_returns_zero_for_empty
127+
assert_equal 0, HTTP::FormData::CompositeIO.new([]).size
120128
end
121129

122-
# --- Kill mutations for CompositeIO#initialize ---
123-
124-
# Kill: replacing io.is_a?(String) with nil/false/self.is_a?(String)/io.instance_of?(String)
125-
# Verify that a String subclass is also converted to StringIO
126-
def test_initialize_converts_string_subclass_to_io
127-
str_subclass = Class.new(String)
128-
io = HTTP::FormData::CompositeIO.new([str_subclass.new("hello")])
130+
# String subclass is also converted to StringIO
131+
def test_accepts_string_subclass
132+
io = HTTP::FormData::CompositeIO.new([Class.new(String).new("hello")])
129133

130134
assert_equal "hello", io.read
131135
end
132136

133-
# Kill: replacing io.respond_to?(:read) with nil/false
134-
# Verify that an object responding to :read is accepted as-is
135-
def test_initialize_accepts_custom_io_object
137+
# Object responding to :read is accepted as-is
138+
def test_accepts_custom_io_object
136139
custom_io = Class.new do
137-
def initialize
138-
@done = false
139-
end
140+
def initialize = @done = false
140141

141142
def read(length = nil, outbuf = nil)
142143
if @done
143144
length ? nil : ""
144145
else
145146
@done = true
146147
result = +"custom"
147-
if outbuf
148-
outbuf.replace(result)
149-
outbuf
150-
else
151-
result
152-
end
148+
outbuf ? outbuf.replace(result) : result
153149
end
154150
end
155151

156152
def size = 6
157-
158-
def rewind
159-
@done = false
160-
end
153+
def rewind = @done = false
161154
end.new
162155

163-
io = HTTP::FormData::CompositeIO.new([custom_io])
164-
165-
assert_equal "custom", io.read
156+
assert_equal "custom", HTTP::FormData::CompositeIO.new([custom_io]).read
166157
end
167158

168-
# Kill: @index = 0 replaced with @index = nil or removed
169-
def test_initialize_starts_reading_from_beginning
170-
io = HTTP::FormData::CompositeIO.new(%w[abc def])
171-
172-
assert_equal "a", io.read(1)
159+
def test_starts_reading_from_beginning
160+
assert_equal "a", HTTP::FormData::CompositeIO.new(%w[abc def]).read(1)
173161
end
174162

175-
# Kill: @buffer = "".b replaced with something else
176-
def test_read_partial_uses_internal_buffer_correctly
163+
def test_reads_across_io_boundaries
177164
io = HTTP::FormData::CompositeIO.new(%w[abc def])
178165

179166
assert_equal "ab", io.read(2)
180167
assert_equal "cd", io.read(2)
181168
assert_equal "ef", io.read(2)
182169
end
183170

184-
# Kill: replacing current_io with nil or other mutation
185-
def test_current_io_returns_nil_after_all_read
171+
def test_returns_nil_after_all_read_with_length
186172
io = HTTP::FormData::CompositeIO.new(["a"])
187173
io.read
188174

189175
assert_nil io.read(1)
190176
end
191177

192-
# --- Kill mutations for CompositeIO#read ---
193-
194-
# Kill: data unless length && data.empty? — various mutations
195-
# When reading all with no length and there IS data
196178
def test_read_without_length_returns_string
197179
io = HTTP::FormData::CompositeIO.new(["hello"])
198180
result = io.read
@@ -201,48 +183,15 @@ def test_read_without_length_returns_string
201183
assert_equal "hello", result
202184
end
203185

204-
# When reading all with no length and NO data left
205-
def test_read_without_length_returns_empty_string_when_exhausted
206-
io = HTTP::FormData::CompositeIO.new(["hello"])
207-
io.read
208-
result = io.read
209-
210-
assert_instance_of String, result
211-
assert_equal "", result
212-
end
213-
214-
# When reading with length and NO data left
215-
def test_read_with_length_returns_nil_when_exhausted
216-
io = HTTP::FormData::CompositeIO.new(["hello"])
217-
io.read
218-
result = io.read(5)
219-
220-
assert_nil result
221-
end
222-
223-
# --- Kill mutations for CompositeIO#readpartial ---
224-
225-
# Kill: chunk && !chunk.empty? mutations
226-
# Read across IO boundary with exact length
227-
def test_readpartial_advances_through_ios
228-
io = HTTP::FormData::CompositeIO.new(%w[ab cd ef])
229-
230-
assert_equal "abcdef", io.read
186+
def test_advances_through_ios
187+
assert_equal "abcdef", HTTP::FormData::CompositeIO.new(%w[ab cd ef]).read
231188
end
232189

233-
# Kill: chunk = current_io.read(max_length, @buffer) mutations
234-
def test_readpartial_with_empty_io_in_middle
235-
io = HTTP::FormData::CompositeIO.new(["ab", "", "cd"])
236-
237-
assert_equal "abcd", io.read
190+
def test_skips_empty_io_in_middle
191+
assert_equal "abcd", HTTP::FormData::CompositeIO.new(["ab", "", "cd"]).read
238192
end
239193

240-
# --- Kill mutations for CompositeIO#read_chunks ---
241-
242-
# Kill: next if length.nil? replaced with other
243-
# Kill: length -= chunk.bytesize mutations
244-
# Kill: break if length.zero? mutations
245-
def test_read_chunks_respects_length_exactly
194+
def test_respects_length_exactly
246195
io = HTTP::FormData::CompositeIO.new(%w[abcdef ghijkl])
247196

248197
assert_equal "abc", io.read(3)
@@ -252,14 +201,13 @@ def test_read_chunks_respects_length_exactly
252201
assert_nil io.read(1)
253202
end
254203

255-
def test_read_chunks_length_spanning_ios
204+
def test_length_spanning_ios
256205
io = HTTP::FormData::CompositeIO.new(%w[ab cd ef])
257206

258207
assert_equal "abcd", io.read(4)
259208
assert_equal "ef", io.read(4)
260209
end
261210

262-
# Kill: read with outbuf and no length
263211
def test_read_all_with_outbuf
264212
outbuf = +""
265213
io = HTTP::FormData::CompositeIO.new(["hello", " ", "world"])
@@ -270,7 +218,6 @@ def test_read_all_with_outbuf
270218
assert_same result, outbuf
271219
end
272220

273-
# Verify outbuf is cleared before use
274221
def test_read_with_outbuf_clears_previous_content
275222
outbuf = +"previous content"
276223
io = HTTP::FormData::CompositeIO.new(["new"])
@@ -279,25 +226,12 @@ def test_read_with_outbuf_clears_previous_content
279226
assert_equal "new", outbuf
280227
end
281228

282-
# Kill: outbuf.clear.force_encoding(Encoding::BINARY) -> outbuf.clear
283-
# Verify that outbuf encoding is forced to binary
284-
def test_read_with_utf8_outbuf_returns_binary_encoding
229+
def test_outbuf_encoding_forced_to_binary
285230
outbuf = +"hello"
286231
outbuf.force_encoding(Encoding::UTF_8)
287232
io = HTTP::FormData::CompositeIO.new(%w[Marohnić])
288233
io.read(5, outbuf)
289234

290235
assert_equal Encoding::BINARY, outbuf.encoding
291236
end
292-
293-
# Kill: error message mutation — io.inspect vs io vs nil vs self.inspect
294-
def test_error_message_contains_inspect_of_invalid_io
295-
obj = Object.new
296-
def obj.inspect = "INVALID_IO_INSPECT"
297-
def obj.to_s = "INVALID_IO_TO_S"
298-
299-
error = assert_raises(ArgumentError) { HTTP::FormData::CompositeIO.new([obj]) }
300-
301-
assert_includes error.message, "INVALID_IO_INSPECT"
302-
end
303237
end

0 commit comments

Comments
 (0)