github.com/99designs/gqlgen@v0.17.45/client/withfilesoption_test.go (about) 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 // returns `{"0":["variables.input.files.0"],"1":["variables.input.files.1"]}` 95 // but the order of file inputs is unpredictable between different OS systems 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 // returns `{"0":["variables.req.files.0"],"1":["variables.req.files.1"],"2":["variables.req.foo.bar"]}` 148 // but the order of file inputs is unpredictable between different OS systems 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 // returns `{"0":["variables.files.0","variables.files.2"],"1":["variables.files.1"]}` 208 // but the order of file inputs is unpredictable between different OS systems 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 }