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