Skip to content

Commit 8becb68

Browse files
committed
Added multipart tests
1 parent 4c7b840 commit 8becb68

File tree

2 files changed

+292
-2
lines changed

2 files changed

+292
-2
lines changed

pkg/multipart/multipart.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,15 @@ func (enc *Encoder) writeField(name string, value any) error {
214214
// remain a single value).
215215
if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
216216
if (rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array) && rv.Type().Elem().Kind() == reflect.Uint8 {
217-
// Treat []byte as a single scalar value
218-
rv = reflect.ValueOf(string(rv.Bytes()))
217+
// Treat []byte and [N]byte as a single scalar value (convert to string)
218+
// Build a byte slice from the array or slice
219+
var byteSlice []byte
220+
for i := 0; i < rv.Len(); i++ {
221+
byteSlice = append(byteSlice, rv.Index(i).Interface().(byte))
222+
}
223+
rv = reflect.ValueOf(string(byteSlice))
219224
} else {
225+
// Iterate over all elements and write each as a separate form field.
220226
var result error
221227
for i := 0; i < rv.Len(); i++ {
222228
if err := enc.writeField(name, rv.Index(i).Interface()); err != nil {

pkg/multipart/multipart_test.go

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
package multipart
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"mime/multipart"
7+
"strings"
8+
"testing"
9+
)
10+
11+
///////////////////////////////////////////////////////////////////////////////
12+
// TEST TYPES
13+
14+
// testRequest represents a struct for testing various field types
15+
type testRequest struct {
16+
Name string `json:"name"`
17+
Tags []string `json:"tags,omitempty"`
18+
MultiTags []string `json:"multi_tags"`
19+
Numbers []int `json:"numbers,omitempty"`
20+
SingleNumber []int `json:"single_number"`
21+
ByteData []byte `json:"byte_data,omitempty"`
22+
ByteArray [4]byte `json:"byte_array,omitempty"`
23+
StringArray [3]string `json:"string_array,omitempty"`
24+
}
25+
26+
///////////////////////////////////////////////////////////////////////////////
27+
// TESTS
28+
29+
func Test_Multipart_EmptySlice(t *testing.T) {
30+
req := testRequest{
31+
Name: "test",
32+
Tags: []string{}, // Empty slice with omitempty
33+
}
34+
35+
buf := new(bytes.Buffer)
36+
enc := NewMultipartEncoder(buf)
37+
defer enc.Close()
38+
39+
if err := enc.Encode(req); err != nil {
40+
t.Fatalf("encode error: %v", err)
41+
}
42+
43+
// Empty slices with omitempty should not produce form fields
44+
content := buf.String()
45+
if strings.Contains(content, "tags") {
46+
t.Error("empty slice with omitempty should not produce a form field")
47+
}
48+
}
49+
50+
func Test_Multipart_SingleElementSlice(t *testing.T) {
51+
req := testRequest{
52+
Name: "test",
53+
Tags: []string{"golang"},
54+
}
55+
56+
buf := new(bytes.Buffer)
57+
enc := NewMultipartEncoder(buf)
58+
defer enc.Close()
59+
60+
if err := enc.Encode(req); err != nil {
61+
t.Fatalf("encode error: %v", err)
62+
}
63+
64+
content := buf.String()
65+
if !strings.Contains(content, "tags") || !strings.Contains(content, "golang") {
66+
t.Error("single-element slice should produce one form field")
67+
}
68+
}
69+
70+
func Test_Multipart_MultiElementSlice(t *testing.T) {
71+
req := testRequest{
72+
Name: "test",
73+
Tags: []string{"golang", "testing", "multipart"},
74+
}
75+
76+
buf := new(bytes.Buffer)
77+
enc := NewMultipartEncoder(buf)
78+
defer enc.Close()
79+
80+
if err := enc.Encode(req); err != nil {
81+
t.Fatalf("encode error: %v", err)
82+
}
83+
84+
content := buf.String()
85+
// Should contain multiple tags fields
86+
count := strings.Count(content, `Content-Disposition: form-data; name="tags"`)
87+
if count != 3 {
88+
t.Errorf("expected 3 tags fields, got %d", count)
89+
}
90+
if !strings.Contains(content, "golang") || !strings.Contains(content, "testing") || !strings.Contains(content, "multipart") {
91+
t.Error("all slice elements should be present in form fields")
92+
}
93+
}
94+
95+
func Test_Multipart_SliceOfIntegers(t *testing.T) {
96+
req := testRequest{
97+
Name: "test",
98+
SingleNumber: []int{42, 100},
99+
}
100+
101+
buf := new(bytes.Buffer)
102+
enc := NewMultipartEncoder(buf)
103+
defer enc.Close()
104+
105+
if err := enc.Encode(req); err != nil {
106+
t.Fatalf("encode error: %v", err)
107+
}
108+
109+
content := buf.String()
110+
// Should contain two single_number fields with integer values
111+
count := strings.Count(content, `Content-Disposition: form-data; name="single_number"`)
112+
if count != 2 {
113+
t.Errorf("expected 2 single_number fields, got %d", count)
114+
}
115+
if !strings.Contains(content, "42") || !strings.Contains(content, "100") {
116+
t.Error("integer values should be present in form fields")
117+
}
118+
}
119+
120+
func Test_Multipart_ByteSlice(t *testing.T) {
121+
req := testRequest{
122+
Name: "test",
123+
ByteData: []byte("hello"),
124+
}
125+
126+
buf := new(bytes.Buffer)
127+
enc := NewMultipartEncoder(buf)
128+
defer enc.Close()
129+
130+
if err := enc.Encode(req); err != nil {
131+
t.Fatalf("encode error: %v", err)
132+
}
133+
134+
content := buf.String()
135+
// []byte should be treated as a single scalar value (string)
136+
count := strings.Count(content, `Content-Disposition: form-data; name="byte_data"`)
137+
if count != 1 {
138+
t.Errorf("[]byte should produce exactly 1 field, got %d", count)
139+
}
140+
if !strings.Contains(content, "hello") {
141+
t.Error("byte slice should be converted to string and present in form field")
142+
}
143+
}
144+
145+
func Test_Multipart_ByteArray(t *testing.T) {
146+
req := testRequest{
147+
Name: "test",
148+
ByteArray: [4]byte{'t', 'e', 's', 't'},
149+
}
150+
151+
buf := new(bytes.Buffer)
152+
enc := NewMultipartEncoder(buf)
153+
defer enc.Close()
154+
155+
if err := enc.Encode(req); err != nil {
156+
t.Fatalf("encode error: %v", err)
157+
}
158+
159+
content := buf.String()
160+
// [N]byte should also be treated as a single scalar value (string)
161+
count := strings.Count(content, `Content-Disposition: form-data; name="byte_array"`)
162+
if count != 1 {
163+
t.Errorf("[N]byte should produce exactly 1 field, got %d", count)
164+
}
165+
if !strings.Contains(content, "test") {
166+
t.Error("byte array should be converted to string and present in form field")
167+
}
168+
}
169+
170+
func Test_Multipart_StringArray(t *testing.T) {
171+
req := testRequest{
172+
Name: "test",
173+
StringArray: [3]string{"first", "second", "third"},
174+
}
175+
176+
buf := new(bytes.Buffer)
177+
enc := NewMultipartEncoder(buf)
178+
defer enc.Close()
179+
180+
if err := enc.Encode(req); err != nil {
181+
t.Fatalf("encode error: %v", err)
182+
}
183+
184+
content := buf.String()
185+
// String array should produce 3 separate form fields
186+
count := strings.Count(content, `Content-Disposition: form-data; name="string_array"`)
187+
if count != 3 {
188+
t.Errorf("expected 3 string_array fields, got %d", count)
189+
}
190+
if !strings.Contains(content, "first") || !strings.Contains(content, "second") || !strings.Contains(content, "third") {
191+
t.Error("all array elements should be present in form fields")
192+
}
193+
}
194+
195+
func Test_Multipart_EmptySliceWithoutOmitempty(t *testing.T) {
196+
req := testRequest{
197+
Name: "test",
198+
MultiTags: []string{}, // Empty slice without omitempty
199+
}
200+
201+
buf := new(bytes.Buffer)
202+
enc := NewMultipartEncoder(buf)
203+
defer enc.Close()
204+
205+
if err := enc.Encode(req); err != nil {
206+
t.Fatalf("encode error: %v", err)
207+
}
208+
209+
content := buf.String()
210+
// Empty slice without omitempty should still not produce a field
211+
// (current behavior treats empty and absent the same)
212+
if strings.Contains(content, "multi_tags") {
213+
t.Log("Note: empty slice without omitempty currently does not produce a field")
214+
}
215+
}
216+
217+
func Test_Form_MultiElementSlice(t *testing.T) {
218+
// Test form encoding (not multipart) with repeated fields
219+
req := testRequest{
220+
Name: "test",
221+
Tags: []string{"a", "b", "c"},
222+
}
223+
224+
buf := new(bytes.Buffer)
225+
enc := NewFormEncoder(buf)
226+
if err := enc.Encode(req); err != nil {
227+
t.Fatalf("encode error: %v", err)
228+
}
229+
if err := enc.Close(); err != nil {
230+
t.Fatalf("close error: %v", err)
231+
}
232+
233+
content := buf.String()
234+
// URL-encoded form should support repeated fields using Add()
235+
// Expected format: name=test&tags=a&tags=b&tags=c (order may vary)
236+
if !strings.Contains(content, "name=test") {
237+
t.Errorf("name field should be present, got: %q", content)
238+
}
239+
// Count occurrences of tags= in the form data
240+
tagCount := strings.Count(content, "tags=")
241+
if tagCount != 3 {
242+
t.Errorf("expected 3 tags fields in form data, got %d, content: %q", tagCount, content)
243+
}
244+
}
245+
246+
///////////////////////////////////////////////////////////////////////////////
247+
// HELPER FUNCTIONS
248+
249+
// parseFormValues extracts form field values from multipart content
250+
func parseFormValues(t *testing.T, content string, fieldName string) []string {
251+
// This is a simplified parser for testing purposes
252+
reader := multipart.NewReader(strings.NewReader(content), extractBoundary(content))
253+
var values []string
254+
255+
for {
256+
part, err := reader.NextPart()
257+
if err == io.EOF {
258+
break
259+
}
260+
if err != nil {
261+
t.Fatalf("parse error: %v", err)
262+
}
263+
264+
if part.FormName() == fieldName {
265+
data, err := io.ReadAll(part)
266+
if err != nil {
267+
t.Fatalf("read error: %v", err)
268+
}
269+
values = append(values, string(data))
270+
}
271+
}
272+
273+
return values
274+
}
275+
276+
// extractBoundary extracts the boundary from a multipart message
277+
func extractBoundary(content string) string {
278+
parts := strings.Split(content, "\r\n")
279+
if len(parts) > 0 {
280+
boundary := strings.TrimPrefix(parts[0], "--")
281+
return boundary
282+
}
283+
return ""
284+
}

0 commit comments

Comments
 (0)