Test for handler with file upload

760 Views Asked by At

I'm trying to write a test for a handler which receives a file. As part of such I'm trying to configure my context so the handler can use it.

My intention is to create a file and use multipart.FileHeader to open it.

f, err := os.CreateTemp("", "upload-test")
require.NoError(t, err)
_, err = f.Write([]byte("1234"))
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)

fileHeader := &multipart.FileHeader{
    Filename: f.Name(),
    Size:     4,
}
open, err := fileHeader.Open()
require.NoError(t, err)

However the Open method returns: "open : no such file or directory"

UPDATE:

My endpoint receives the below struct

type UploadDatabaseRequest struct {
    Group    string                `form:"group" binding:"required"`
    Database *multipart.FileHeader `form:"database" binding:"required"`
}

Based on Ivan 's answer I tried to read the request using multipart.NewReader so I could get a *multipart.FileHeader. However the below code results in "unexpected EOF" from the call to multipartReader.ReadForm(100000000).

contentType := multipartWriter.FormDataContentType()
_, params, err := mime.ParseMediaType(contentType)
assert.NoError(t, err)

multipartReader := multipart.NewReader(bytes.NewReader(buf.Bytes()), params["boundary"])
form, err := multipartReader.ReadForm(100000000)
require.NoError(t, err)
fileHeader := form.File["file"][0]
uploadRequest := &UploadDatabaseRequest{
    Group:    groupName,
    Database: fileHeader,
}
1

There are 1 best solutions below

3
On

I created an example to demonstrate how it should work. First, let me present the code, then, I'll walk you through all of the relevant parts.

Upload

The upload is done in the handlers_test.go file. I wrote two tests to show off how to create a valid HTTP Request with a multipart body (I assumed that the communication was based on HTTP). Here you can find the code:

package multipart

import (
    "bytes"
    "io"
    "mime/multipart"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestHandleFile(t *testing.T) {
    t.Run("MultipartRequest", func(t *testing.T) {
        // instantiate multipart request
        var buf bytes.Buffer
        multipartWriter := multipart.NewWriter(&buf)
        defer multipartWriter.Close()

        // add form field
        filePart, _ := multipartWriter.CreateFormFile("file", "file.txt")
        filePart.Write([]byte("Hello, World!"))

        r := httptest.NewRequest(http.MethodPost, "/file", &buf)
        w := httptest.NewRecorder()

        r.Header.Set("Content-Type", multipartWriter.FormDataContentType())

        HandleFile(w, r)

        data, _ := io.ReadAll(w.Result().Body)

        assert.Equal(t, http.StatusOK, w.Result().StatusCode)
        assert.Equal(t, []byte("Hello, World!"), data)
    })

    t.Run("PlainRequest", func(t *testing.T) {
        r := httptest.NewRequest(http.MethodPost, "/file", nil)
        w := httptest.NewRecorder()

        HandleFile(w, r)

        assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
    })
}

We can focus on the subtest MultipartRequest. First, it instantiates a multipart body which will be used later as the request payload of the HTTP request we're going to send. Then, we create a file part and write dummy content to it. Before sending out the request, we've to see the Content-Type header that will be used for parsing stuff. The rest of the test should be pretty straightforward.

Read

The read (or parsing) is done by the HTTP server. The file involved is the handlers.go file:

package multipart

import (
    "io"
    "mime"
    "mime/multipart"
    "net/http"
    "strings"
)

func HandleFile(w http.ResponseWriter, r *http.Request) {
    mediaType, params, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
    if strings.HasPrefix(mediaType, "multipart/") {
        multipartReader := multipart.NewReader(r.Body, params["boundary"])
        filePart, _ := multipartReader.NextPart()
        // pay attention here when you read large file
        data, _ := io.ReadAll(filePart)
        w.Write(data)
        return
    }
    w.WriteHeader(http.StatusBadRequest)
    w.Write([]byte("request is not multipart"))
}

Here, the relevant steps can be summarized in the following list:

  1. We retrieve and parse the Content-Type header from the HTTP Request
  2. We check if the above value starts with the string multipart/
  3. If so, we read the next (and only) part of the body and we write its content to the response stream
  4. If not, we return a BadRequest error to the HTTP client

In the code I put some comments to explain some delicate sections that deserve attention. Furthermore, I simplified the codebase by not handling any error that might happen.
Hope to help in solving your issue, let me know!