gographqlgqlgen

How to UT upload files


I'm working on making UT for my GraphQL API. I need to test a mutation where I upload a file. I am using gqlgen on this project.

...
localFile, err := os.Open("./file.xlsx")
if err != nil {
    fmt.Errorf(err.Error())
}

c.MustPost(queries.UPLOAD_CSV, &resp, client.Var("id", id), client.Var("file", localFile), client.AddHeader("Authorization", "Bearer "+hub.AccessToken))

c.MustPost panic and sends an error:

--- FAIL: TestUploadCSV (0.00s)
panic: [{"message":"map[string]interface {} is not an Upload","path":["uploadCSV","file"]}] [recovered]
panic: [{"message":"map[string]interface {} is not an Upload","path":["uploadCSV","file"]}]

How can I send the localFile to my API? I thought about making it through curl, but I'm not sure if it's a clean way to do so.


Solution

  • You cannot just pass the os.File like that. You need to actually read the file, construct MIME multi-part request body (see spec) and send it in POST request.

    buf := &bytes.Buffer{}
    w := multipart.NewWriter(...)
    
    // add other required fields (operations, map) here
    
    // load file (you can do these directly I am emphasizing them 
    // as variables so code below is more understandable
    fileKey := "0" // file key in 'map'
    fileName := "file.xslx" // file name
    fileContentType := "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    fileContents, err := ioutil.ReadFile("./file.xlsx")
    // ...
    
    // make multipart body
    h := make(textproto.MIMEHeader)
    
    h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fileKey, fileName))
    h.Set("Content-Type", fileContentType)
    ff, err := bodyWriter.CreatePart(h)
    // ...
    _, err = ff.Write(fileContents)
    // ...
    err = bodyWriter.Close()
    // ...
    
    req, err := http.NewRequest("POST", fmt.Sprintf("https://endpoint"), buf)
    //...
    

    There is a good working example of doing this in gqlgen repository itself here: example/fileupload/fileupload_test.go.

    In that example each file is loaded into (and represented by) file struct type defined on line I linked which might make it a bit confusing at first sight.