测试带有文件上传的处理程序

huangapple go评论90阅读模式
英文:

Test for handler with file upload

问题

我正在尝试为一个接收文件的处理程序编写一个测试。作为其中的一部分,我正在尝试配置上下文,以便处理程序可以使用它。

我的意图是创建一个文件并使用multipart.FileHeader来打开它。

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)

然而,Open 方法返回:"open: 没有该文件或目录"

更新:

我的端点接收以下结构体:

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

根据Ivan的答案,我尝试使用multipart.NewReader来读取请求,以便获得一个*multipart.FileHeader。然而,下面的代码在调用multipartReader.ReadForm(100000000)时会导致"unexpected EOF"的错误。

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,
}
英文:

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

得分: 1

我创建了一个示例来演示它应该如何工作。首先,让我介绍一下代码,然后我会逐步解释所有相关部分。

上传

上传操作在handlers_test.go文件中完成。我编写了两个测试来展示如何创建一个带有多部分主体的有效HTTP请求(我假设通信是基于HTTP的)。以下是代码:

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) {
		// 实例化多部分请求
		var buf bytes.Buffer
		multipartWriter := multipart.NewWriter(&buf)
		defer multipartWriter.Close()

		// 添加表单字段
		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)
	})
}

我们可以关注子测试MultipartRequest。首先,它实例化一个多部分主体,稍后将用作我们将要发送的HTTP请求的请求有效负载。然后,我们创建一个文件部分并向其写入虚拟内容。在发送请求之前,我们需要查看将用于解析内容的Content-Type头部。测试的其余部分应该很简单。

读取

读取(或解析)由HTTP服务器完成。涉及的文件是handlers.go文件:

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()
		// 注意在读取大文件时
		data, _ := io.ReadAll(filePart)
		w.Write(data)
		return
	}
	w.WriteHeader(http.StatusBadRequest)
	w.Write([]byte("request is not multipart"))
}

在这里,相关步骤可以总结如下:

  1. 我们从HTTP请求中检索并解析Content-Type头部
  2. 我们检查上述值是否以字符串multipart/开头
  3. 如果是,我们读取主体的下一个(也是唯一的)部分,并将其内容写入响应流
  4. 如果不是,我们向HTTP客户端返回BadRequest错误

在代码中,我添加了一些注释来解释一些需要注意的关键部分。此外,我简化了代码库,没有处理可能发生的任何错误。

希望能帮助解决您的问题,如果有任何疑问,请告诉我!

英文:

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!

huangapple
  • 本文由 发表于 2023年1月19日 14:20:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75168418.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定