github.com/maeglindeveloper/gqlgen@v0.13.1-0.20210413081235-57808b12a0a0/example/fileupload/fileupload_test.go (about) 1 //go:generate go run ../../testdata/gqlgen.go -stub stubs.go 2 package fileupload 3 4 import ( 5 "bytes" 6 "context" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "mime/multipart" 11 "net/http" 12 "net/http/httptest" 13 "net/textproto" 14 "testing" 15 16 "github.com/99designs/gqlgen/example/fileupload/model" 17 "github.com/99designs/gqlgen/graphql" 18 "github.com/99designs/gqlgen/graphql/handler" 19 "github.com/99designs/gqlgen/graphql/handler/transport" 20 "github.com/stretchr/testify/require" 21 ) 22 23 func TestFileUpload(t *testing.T) { 24 client := http.Client{} 25 26 t.Run("valid single file upload", func(t *testing.T) { 27 resolver := &Stub{} 28 resolver.MutationResolver.SingleUpload = func(ctx context.Context, file graphql.Upload) (*model.File, error) { 29 require.NotNil(t, file) 30 require.NotNil(t, file.File) 31 content, err := ioutil.ReadAll(file.File) 32 require.Nil(t, err) 33 require.Equal(t, string(content), "test") 34 35 return &model.File{ 36 ID: 1, 37 Name: file.Filename, 38 Content: string(content), 39 ContentType: file.ContentType, 40 }, nil 41 } 42 srv := httptest.NewServer(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolver}))) 43 defer srv.Close() 44 45 operations := `{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id, name, content, contentType } }", "variables": { "file": null } }` 46 mapData := `{ "0": ["variables.file"] }` 47 files := []file{ 48 { 49 mapKey: "0", 50 name: "a.txt", 51 content: "test", 52 contentType: "text/plain", 53 }, 54 } 55 req := createUploadRequest(t, srv.URL, operations, mapData, files) 56 57 resp, err := client.Do(req) 58 require.Nil(t, err) 59 require.Equal(t, http.StatusOK, resp.StatusCode) 60 responseBody, err := ioutil.ReadAll(resp.Body) 61 require.Nil(t, err) 62 responseString := string(responseBody) 63 require.Equal(t, `{"data":{"singleUpload":{"id":1,"name":"a.txt","content":"test","contentType":"text/plain"}}}`, responseString) 64 err = resp.Body.Close() 65 require.Nil(t, err) 66 }) 67 68 t.Run("valid single file upload with payload", func(t *testing.T) { 69 resolver := &Stub{} 70 resolver.MutationResolver.SingleUploadWithPayload = func(ctx context.Context, req model.UploadFile) (*model.File, error) { 71 require.Equal(t, req.ID, 1) 72 require.NotNil(t, req.File) 73 require.NotNil(t, req.File.File) 74 content, err := ioutil.ReadAll(req.File.File) 75 require.Nil(t, err) 76 require.Equal(t, string(content), "test") 77 78 return &model.File{ 79 ID: 1, 80 Name: req.File.Filename, 81 Content: string(content), 82 ContentType: req.File.ContentType, 83 }, nil 84 } 85 srv := httptest.NewServer(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolver}))) 86 defer srv.Close() 87 88 operations := `{ "query": "mutation ($req: UploadFile!) { singleUploadWithPayload(req: $req) { id, name, content, contentType } }", "variables": { "req": {"file": null, "id": 1 } } }` 89 mapData := `{ "0": ["variables.req.file"] }` 90 files := []file{ 91 { 92 mapKey: "0", 93 name: "a.txt", 94 content: "test", 95 contentType: "text/plain", 96 }, 97 } 98 req := createUploadRequest(t, srv.URL, operations, mapData, files) 99 100 resp, err := client.Do(req) 101 require.Nil(t, err) 102 require.Equal(t, http.StatusOK, resp.StatusCode) 103 responseBody, err := ioutil.ReadAll(resp.Body) 104 require.Nil(t, err) 105 require.Equal(t, `{"data":{"singleUploadWithPayload":{"id":1,"name":"a.txt","content":"test","contentType":"text/plain"}}}`, string(responseBody)) 106 err = resp.Body.Close() 107 require.Nil(t, err) 108 }) 109 110 t.Run("valid file list upload", func(t *testing.T) { 111 resolver := &Stub{} 112 resolver.MutationResolver.MultipleUpload = func(ctx context.Context, files []*graphql.Upload) ([]*model.File, error) { 113 require.Len(t, files, 2) 114 var contents []string 115 var resp []*model.File 116 for i := range files { 117 require.NotNil(t, files[i].File) 118 content, err := ioutil.ReadAll(files[i].File) 119 require.Nil(t, err) 120 contents = append(contents, string(content)) 121 resp = append(resp, &model.File{ 122 ID: i + 1, 123 Name: files[i].Filename, 124 Content: string(content), 125 ContentType: files[i].ContentType, 126 }) 127 } 128 require.ElementsMatch(t, []string{"test1", "test2"}, contents) 129 return resp, nil 130 } 131 srv := httptest.NewServer(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolver}))) 132 defer srv.Close() 133 134 operations := `{ "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id, name, content, contentType } }", "variables": { "files": [null, null] } }` 135 mapData := `{ "0": ["variables.files.0"], "1": ["variables.files.1"] }` 136 files := []file{ 137 { 138 mapKey: "0", 139 name: "a.txt", 140 content: "test1", 141 contentType: "text/plain", 142 }, 143 { 144 mapKey: "1", 145 name: "b.txt", 146 content: "test2", 147 contentType: "text/plain", 148 }, 149 } 150 req := createUploadRequest(t, srv.URL, operations, mapData, files) 151 152 resp, err := client.Do(req) 153 require.Nil(t, err) 154 require.Equal(t, http.StatusOK, resp.StatusCode) 155 responseBody, err := ioutil.ReadAll(resp.Body) 156 require.Nil(t, err) 157 require.Equal(t, `{"data":{"multipleUpload":[{"id":1,"name":"a.txt","content":"test1","contentType":"text/plain"},{"id":2,"name":"b.txt","content":"test2","contentType":"text/plain"}]}}`, string(responseBody)) 158 err = resp.Body.Close() 159 require.Nil(t, err) 160 }) 161 162 t.Run("valid file list upload with payload", func(t *testing.T) { 163 resolver := &Stub{} 164 resolver.MutationResolver.MultipleUploadWithPayload = func(ctx context.Context, req []*model.UploadFile) ([]*model.File, error) { 165 require.Len(t, req, 2) 166 var ids []int 167 var contents []string 168 var resp []*model.File 169 for i := range req { 170 require.NotNil(t, req[i].File) 171 require.NotNil(t, req[i].File.File) 172 content, err := ioutil.ReadAll(req[i].File.File) 173 require.Nil(t, err) 174 ids = append(ids, req[i].ID) 175 contents = append(contents, string(content)) 176 resp = append(resp, &model.File{ 177 ID: i + 1, 178 Name: req[i].File.Filename, 179 Content: string(content), 180 ContentType: req[i].File.ContentType, 181 }) 182 } 183 require.ElementsMatch(t, []int{1, 2}, ids) 184 require.ElementsMatch(t, []string{"test1", "test2"}, contents) 185 return resp, nil 186 } 187 srv := httptest.NewServer(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolver}))) 188 defer srv.Close() 189 190 operations := `{ "query": "mutation($req: [UploadFile!]!) { multipleUploadWithPayload(req: $req) { id, name, content, contentType } }", "variables": { "req": [ { "id": 1, "file": null }, { "id": 2, "file": null } ] } }` 191 mapData := `{ "0": ["variables.req.0.file"], "1": ["variables.req.1.file"] }` 192 files := []file{ 193 { 194 mapKey: "0", 195 name: "a.txt", 196 content: "test1", 197 contentType: "text/plain", 198 }, 199 { 200 mapKey: "1", 201 name: "b.txt", 202 content: "test2", 203 contentType: "text/plain", 204 }, 205 } 206 req := createUploadRequest(t, srv.URL, operations, mapData, files) 207 208 resp, err := client.Do(req) 209 require.Nil(t, err) 210 require.Equal(t, http.StatusOK, resp.StatusCode) 211 responseBody, err := ioutil.ReadAll(resp.Body) 212 require.Nil(t, err) 213 require.Equal(t, `{"data":{"multipleUploadWithPayload":[{"id":1,"name":"a.txt","content":"test1","contentType":"text/plain"},{"id":2,"name":"b.txt","content":"test2","contentType":"text/plain"}]}}`, string(responseBody)) 214 err = resp.Body.Close() 215 require.Nil(t, err) 216 }) 217 218 t.Run("valid file list upload with payload and file reuse", func(t *testing.T) { 219 resolver := &Stub{} 220 resolver.MutationResolver.MultipleUploadWithPayload = func(ctx context.Context, req []*model.UploadFile) ([]*model.File, error) { 221 require.Len(t, req, 2) 222 var ids []int 223 var contents []string 224 var resp []*model.File 225 for i := range req { 226 require.NotNil(t, req[i].File) 227 require.NotNil(t, req[i].File.File) 228 ids = append(ids, req[i].ID) 229 230 var got []byte 231 buf := make([]byte, 2) 232 for { 233 n, err := req[i].File.File.Read(buf) 234 got = append(got, buf[:n]...) 235 if err != nil { 236 if err == io.EOF { 237 break 238 } 239 require.Fail(t, "unexpected error while reading", err.Error()) 240 } 241 } 242 contents = append(contents, string(got)) 243 resp = append(resp, &model.File{ 244 ID: i + 1, 245 Name: req[i].File.Filename, 246 Content: string(got), 247 ContentType: req[i].File.ContentType, 248 }) 249 } 250 require.ElementsMatch(t, []int{1, 2}, ids) 251 require.ElementsMatch(t, []string{"test1", "test1"}, contents) 252 return resp, nil 253 } 254 255 operations := `{ "query": "mutation($req: [UploadFile!]!) { multipleUploadWithPayload(req: $req) { id, name, content, contentType } }", "variables": { "req": [ { "id": 1, "file": null }, { "id": 2, "file": null } ] } }` 256 mapData := `{ "0": ["variables.req.0.file", "variables.req.1.file"] }` 257 files := []file{ 258 { 259 mapKey: "0", 260 name: "a.txt", 261 content: "test1", 262 contentType: "text/plain", 263 }, 264 } 265 266 test := func(uploadMaxMemory int64) { 267 hndlr := handler.New(NewExecutableSchema(Config{Resolvers: resolver})) 268 hndlr.AddTransport(transport.MultipartForm{MaxMemory: uploadMaxMemory}) 269 270 srv := httptest.NewServer(hndlr) 271 defer srv.Close() 272 req := createUploadRequest(t, srv.URL, operations, mapData, files) 273 resp, err := client.Do(req) 274 require.Nil(t, err) 275 require.Equal(t, http.StatusOK, resp.StatusCode) 276 responseBody, err := ioutil.ReadAll(resp.Body) 277 require.Nil(t, err) 278 require.Equal(t, `{"data":{"multipleUploadWithPayload":[{"id":1,"name":"a.txt","content":"test1","contentType":"text/plain"},{"id":2,"name":"a.txt","content":"test1","contentType":"text/plain"}]}}`, string(responseBody)) 279 err = resp.Body.Close() 280 require.Nil(t, err) 281 } 282 283 t.Run("payload smaller than UploadMaxMemory, stored in memory", func(t *testing.T) { 284 test(5000) 285 }) 286 287 t.Run("payload bigger than UploadMaxMemory, persisted to disk", func(t *testing.T) { 288 test(2) 289 }) 290 }) 291 } 292 293 type file struct { 294 mapKey string 295 name string 296 content string 297 contentType string 298 } 299 300 func createUploadRequest(t *testing.T, url, operations, mapData string, files []file) *http.Request { 301 bodyBuf := &bytes.Buffer{} 302 bodyWriter := multipart.NewWriter(bodyBuf) 303 304 err := bodyWriter.WriteField("operations", operations) 305 require.NoError(t, err) 306 307 err = bodyWriter.WriteField("map", mapData) 308 require.NoError(t, err) 309 310 for i := range files { 311 h := make(textproto.MIMEHeader) 312 h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, files[i].mapKey, files[i].name)) 313 h.Set("Content-Type", files[i].contentType) 314 ff, err := bodyWriter.CreatePart(h) 315 require.NoError(t, err) 316 _, err = ff.Write([]byte(files[i].content)) 317 require.NoError(t, err) 318 } 319 err = bodyWriter.Close() 320 require.NoError(t, err) 321 322 req, err := http.NewRequest("POST", fmt.Sprintf("%s/graphql", url), bodyBuf) 323 require.NoError(t, err) 324 325 req.Header.Set("Content-Type", bodyWriter.FormDataContentType()) 326 return req 327 }