material_manage_system/internal/handler/material_import_test.go

116 lines
3.6 KiB
Go

package handler
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/wmms/wmms/internal/service"
"github.com/wmms/wmms/pkg/response"
)
type countedImportBody struct {
io.Reader
read int64
}
func (body *countedImportBody) Read(p []byte) (int, error) {
n, err := body.Reader.Read(p)
body.read += int64(n)
return n, err
}
func (*countedImportBody) Close() error { return nil }
func TestMaterialImportRejectsLargeBodyBeforeReadingItAll(t *testing.T) {
gin.SetMode(gin.TestMode)
var payload bytes.Buffer
writer := multipart.NewWriter(&payload)
part, err := writer.CreateFormFile("file", "物料.xlsx")
if err != nil {
t.Fatal(err)
}
if _, err := part.Write(bytes.Repeat([]byte("x"), 8<<20)); err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
for _, contentLength := range []int64{int64(payload.Len()), -1} {
t.Run(fmt.Sprintf("content-length=%d", contentLength), func(t *testing.T) {
body := &countedImportBody{Reader: bytes.NewReader(payload.Bytes())}
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
c.Set("organization_id", int64(1))
c.Request = httptest.NewRequest(http.MethodPost, "/material-import", nil)
c.Request.Body = body
c.Request.ContentLength = contentLength
c.Request.Header.Set("Content-Type", writer.FormDataContentType())
(&Handler{}).ImportMaterials(c)
if recorder.Code != http.StatusBadRequest || !strings.Contains(recorder.Body.String(), "5MB") {
t.Fatalf("status=%d, body=%s", recorder.Code, recorder.Body.String())
}
if body.read > service.MaterialImportMaxBytes+(64<<10)+1 {
t.Fatalf("超大请求已读取 %d 字节,应在解析前限制读取", body.read)
}
})
}
}
func TestMaterialImportFileSizeBoundaryAllowsMultipartOverhead(t *testing.T) {
gin.SetMode(gin.TestMode)
for _, size := range []int{service.MaterialImportMaxBytes, service.MaterialImportMaxBytes + 1} {
t.Run(fmt.Sprintf("file-size=%d", size), func(t *testing.T) {
var payload bytes.Buffer
writer := multipart.NewWriter(&payload)
part, err := writer.CreateFormFile("file", "物料.xlsx")
if err != nil {
t.Fatal(err)
}
if _, err := part.Write(bytes.Repeat([]byte("x"), size)); err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
c.Set("organization_id", int64(1))
c.Request = httptest.NewRequest(http.MethodPost, "/material-import", bytes.NewReader(payload.Bytes()))
c.Request.Header.Set("Content-Type", writer.FormDataContentType())
(&Handler{materialImportSvc: service.NewMaterialImportService()}).ImportMaterials(c)
want := "模板格式不正确"
if size > service.MaterialImportMaxBytes {
want = "文件超过 5MB 上限"
}
if recorder.Code != http.StatusBadRequest || !strings.Contains(recorder.Body.String(), want) {
t.Fatalf("status=%d, body=%s, want %s", recorder.Code, recorder.Body.String(), want)
}
})
}
}
func TestErrorWithDataKeepsPayload(t *testing.T) {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
response.ErrorWithData(c, http.StatusBadRequest, "有 1 行校验未通过", map[string]interface{}{
"total_rows": 1, "failed_rows": 1,
})
if recorder.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want 400", recorder.Code)
}
body := recorder.Body.String()
if !strings.Contains(body, "有 1 行校验未通过") || !strings.Contains(body, `"failed_rows":1`) {
t.Fatalf("body = %s", body)
}
}