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