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  }