1 package client_test
2
3 import (
4 "io"
5 "mime"
6 "mime/multipart"
7 "net/http"
8 "os"
9 "regexp"
10 "strings"
11 "testing"
12
13 "github.com/stretchr/testify/require"
14
15 "github.com/99designs/gqlgen/client"
16 )
17
18 func TestWithFiles(t *testing.T) {
19 tempFile1, _ := os.CreateTemp(os.TempDir(), "tempFile1")
20 tempFile2, _ := os.CreateTemp(os.TempDir(), "tempFile2")
21 tempFile3, _ := os.CreateTemp(os.TempDir(), "tempFile3")
22 defer os.Remove(tempFile1.Name())
23 defer os.Remove(tempFile2.Name())
24 defer os.Remove(tempFile3.Name())
25 tempFile1.WriteString(`The quick brown fox jumps over the lazy dog`)
26 tempFile2.WriteString(`hello world`)
27 tempFile3.WriteString(`La-Li-Lu-Le-Lo`)
28
29 t.Run("with one file", func(t *testing.T) {
30 h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
31 mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
32 require.NoError(t, err)
33 require.True(t, strings.HasPrefix(mediaType, "multipart/"))
34
35 mr := multipart.NewReader(r.Body, params["boundary"])
36 for {
37 p, err := mr.NextPart()
38 if err == io.EOF {
39 break
40 }
41 require.NoError(t, err)
42
43 slurp, err := io.ReadAll(p)
44 require.NoError(t, err)
45
46 contentDisposition := p.Header.Get("Content-Disposition")
47
48 if contentDisposition == `form-data; name="operations"` {
49 require.EqualValues(t, `{"query":"{ id }","variables":{"file":{}}}`, slurp)
50 }
51 if contentDisposition == `form-data; name="map"` {
52 require.EqualValues(t, `{"0":["variables.file"]}`, slurp)
53 }
54 if regexp.MustCompile(`form-data; name="0"; filename=.*`).MatchString(contentDisposition) {
55 require.Equal(t, `text/plain; charset=utf-8`, p.Header.Get("Content-Type"))
56 require.EqualValues(t, `The quick brown fox jumps over the lazy dog`, slurp)
57 }
58 }
59 w.Write([]byte(`{}`))
60 })
61
62 c := client.New(h)
63
64 var resp struct{}
65 c.MustPost("{ id }", &resp,
66 client.Var("file", tempFile1),
67 client.WithFiles(),
68 )
69 })
70
71 t.Run("with multiple files", func(t *testing.T) {
72 h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
73 mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
74 require.NoError(t, err)
75 require.True(t, strings.HasPrefix(mediaType, "multipart/"))
76
77 mr := multipart.NewReader(r.Body, params["boundary"])
78 for {
79 p, err := mr.NextPart()
80 if err == io.EOF {
81 break
82 }
83 require.NoError(t, err)
84
85 slurp, err := io.ReadAll(p)
86 require.NoError(t, err)
87
88 contentDisposition := p.Header.Get("Content-Disposition")
89
90 if contentDisposition == `form-data; name="operations"` {
91 require.EqualValues(t, `{"query":"{ id }","variables":{"input":{"files":[{},{}]}}}`, slurp)
92 }
93 if contentDisposition == `form-data; name="map"` {
94
95
96 require.Contains(t, string(slurp), `{"0":`)
97 require.Contains(t, string(slurp), `["variables.input.files.0"]`)
98 require.Contains(t, string(slurp), `,"1":`)
99 require.Contains(t, string(slurp), `["variables.input.files.1"]`)
100 require.Contains(t, string(slurp), `}`)
101 }
102 if regexp.MustCompile(`form-data; name="[0,1]"; filename=.*`).MatchString(contentDisposition) {
103 require.Equal(t, `text/plain; charset=utf-8`, p.Header.Get("Content-Type"))
104 require.Contains(t, []string{
105 `The quick brown fox jumps over the lazy dog`,
106 `hello world`,
107 }, string(slurp))
108 }
109 }
110 w.Write([]byte(`{}`))
111 })
112
113 c := client.New(h)
114
115 var resp struct{}
116 c.MustPost("{ id }", &resp,
117 client.Var("input", map[string]interface{}{
118 "files": []*os.File{tempFile1, tempFile2},
119 }),
120 client.WithFiles(),
121 )
122 })
123
124 t.Run("with multiple files across multiple variables", func(t *testing.T) {
125 h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
126 mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
127 require.NoError(t, err)
128 require.True(t, strings.HasPrefix(mediaType, "multipart/"))
129
130 mr := multipart.NewReader(r.Body, params["boundary"])
131 for {
132 p, err := mr.NextPart()
133 if err == io.EOF {
134 break
135 }
136 require.NoError(t, err)
137
138 slurp, err := io.ReadAll(p)
139 require.NoError(t, err)
140
141 contentDisposition := p.Header.Get("Content-Disposition")
142
143 if contentDisposition == `form-data; name="operations"` {
144 require.EqualValues(t, `{"query":"{ id }","variables":{"req":{"files":[{},{}],"foo":{"bar":{}}}}}`, slurp)
145 }
146 if contentDisposition == `form-data; name="map"` {
147
148
149 require.Contains(t, string(slurp), `{"0":`)
150 require.Contains(t, string(slurp), `["variables.req.files.0"]`)
151 require.Contains(t, string(slurp), `,"1":`)
152 require.Contains(t, string(slurp), `["variables.req.files.1"]`)
153 require.Contains(t, string(slurp), `,"2":`)
154 require.Contains(t, string(slurp), `["variables.req.foo.bar"]`)
155 require.Contains(t, string(slurp), `}`)
156 }
157 if regexp.MustCompile(`form-data; name="[0,1,2]"; filename=.*`).MatchString(contentDisposition) {
158 require.Equal(t, `text/plain; charset=utf-8`, p.Header.Get("Content-Type"))
159 require.Contains(t, []string{
160 `The quick brown fox jumps over the lazy dog`,
161 `La-Li-Lu-Le-Lo`,
162 `hello world`,
163 }, string(slurp))
164 }
165 }
166 w.Write([]byte(`{}`))
167 })
168
169 c := client.New(h)
170
171 var resp struct{}
172 c.MustPost("{ id }", &resp,
173 client.Var("req", map[string]interface{}{
174 "files": []*os.File{tempFile1, tempFile2},
175 "foo": map[string]interface{}{
176 "bar": tempFile3,
177 },
178 }),
179 client.WithFiles(),
180 )
181 })
182
183 t.Run("with multiple files and file reuse", func(t *testing.T) {
184 h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
185 mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
186 require.NoError(t, err)
187 require.True(t, strings.HasPrefix(mediaType, "multipart/"))
188
189 mr := multipart.NewReader(r.Body, params["boundary"])
190 for {
191 p, err := mr.NextPart()
192 if err == io.EOF {
193 break
194 }
195 require.NoError(t, err)
196
197 slurp, err := io.ReadAll(p)
198 require.NoError(t, err)
199
200 contentDisposition := p.Header.Get("Content-Disposition")
201
202 if contentDisposition == `form-data; name="operations"` {
203 require.EqualValues(t, `{"query":"{ id }","variables":{"files":[{},{},{}]}}`, slurp)
204 }
205 if contentDisposition == `form-data; name="map"` {
206 require.EqualValues(t, `{"0":["variables.files.0","variables.files.2"],"1":["variables.files.1"]}`, slurp)
207
208
209 require.Contains(t, string(slurp), `{"0":`)
210 require.Contains(t, string(slurp), `["variables.files.0"`)
211 require.Contains(t, string(slurp), `,"1":`)
212 require.Contains(t, string(slurp), `"variables.files.1"]`)
213 require.Contains(t, string(slurp), `"variables.files.2"]`)
214 require.NotContains(t, string(slurp), `,"2":`)
215 require.Contains(t, string(slurp), `}`)
216 }
217 if regexp.MustCompile(`form-data; name="[0,1]"; filename=.*`).MatchString(contentDisposition) {
218 require.Equal(t, `text/plain; charset=utf-8`, p.Header.Get("Content-Type"))
219 require.Contains(t, []string{
220 `The quick brown fox jumps over the lazy dog`,
221 `hello world`,
222 }, string(slurp))
223 }
224 require.False(t, regexp.MustCompile(`form-data; name="2"; filename=.*`).MatchString(contentDisposition))
225 }
226 w.Write([]byte(`{}`))
227 })
228
229 c := client.New(h)
230
231 var resp struct{}
232 c.MustPost("{ id }", &resp,
233 client.Var("files", []*os.File{tempFile1, tempFile2, tempFile1}),
234 client.WithFiles(),
235 )
236 })
237 }
238
View as plain text