95 lines
3.0 KiB
Go
95 lines
3.0 KiB
Go
// Copyright 2026 tamakyi. All rights reserved.
|
||
// Use of this source code is governed by a MIT-style
|
||
// license that can be found in the LICENSE file.
|
||
|
||
package form
|
||
|
||
import (
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/flamego/flamego"
|
||
"github.com/flamego/template"
|
||
|
||
"github.com/tamakyi/TamaBox/internal/context"
|
||
)
|
||
|
||
// TestBindURLEncodedSkipsFileHeaderField 回归测试:application/x-www-form-urlencoded 提交
|
||
// 到带 []*multipart.FileHeader 字段的结构体时不应 panic。
|
||
//
|
||
// 背景:Bind 的非 multipart 分支曾对所有字段无条件 Set(string),遇到 NewQuestion.Images
|
||
// ([]*multipart.FileHeader)触发 reflect.Set 类型不匹配 panic(提问表单因带
|
||
// enctype="multipart/form-data" 走 multipart 分支而从未触发;独立私聊表单等纯文本提交
|
||
// 会命中此路径,导致提问关闭时提交私聊 500)。
|
||
func TestBindURLEncodedSkipsFileHeaderField(t *testing.T) {
|
||
f := flamego.New()
|
||
// 最小依赖注入:提供项目 context.Context 与 template.Data(Bind 仅用到这两个参数)。
|
||
f.Use(func(ctx flamego.Context) {
|
||
data := template.Data{}
|
||
ctx.Map(context.Context{Context: ctx, Data: data})
|
||
ctx.Map(data)
|
||
})
|
||
|
||
var bound NewQuestion
|
||
f.Post("/", Bind(NewQuestion{}), func(q NewQuestion) {
|
||
bound = q
|
||
})
|
||
|
||
req := httptest.NewRequest(http.MethodPost, "/",
|
||
strings.NewReader("content=你好&start_chat_val=true"))
|
||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||
rec := httptest.NewRecorder()
|
||
f.ServeHTTP(rec, req)
|
||
|
||
if rec.Code >= http.StatusInternalServerError {
|
||
t.Fatalf("unexpected status %d, want < 500 (reflect.Set panic 不应发生)", rec.Code)
|
||
}
|
||
if bound.Content != "你好" {
|
||
t.Fatalf("Content = %q, want %q", bound.Content, "你好")
|
||
}
|
||
if bound.StartChat != "true" {
|
||
t.Fatalf("StartChat = %q, want %q", bound.StartChat, "true")
|
||
}
|
||
if bound.Images != nil {
|
||
t.Fatalf("Images = %v, want nil(非 multipart 请求应跳过图片字段)", bound.Images)
|
||
}
|
||
}
|
||
|
||
// TestBindMultipartStillBindsImages 确认 multipart 分支(提问表单路径)不受影响:
|
||
// 文件字段仍能正常绑定。
|
||
func TestBindMultipartStillBindsImages(t *testing.T) {
|
||
f := flamego.New()
|
||
f.Use(func(ctx flamego.Context) {
|
||
data := template.Data{}
|
||
ctx.Map(context.Context{Context: ctx, Data: data})
|
||
ctx.Map(data)
|
||
})
|
||
|
||
var bound NewQuestion
|
||
f.Post("/", Bind(NewQuestion{}), func(q NewQuestion) {
|
||
bound = q
|
||
})
|
||
|
||
// multipart 请求,携带一个普通字段,无文件
|
||
body := "--BOUND\r\n" +
|
||
"Content-Disposition: form-data; name=\"content\"\r\n\r\n" +
|
||
"hello\r\n" +
|
||
"--BOUND--\r\n"
|
||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
||
req.Header.Set("Content-Type", "multipart/form-data; boundary=BOUND")
|
||
rec := httptest.NewRecorder()
|
||
f.ServeHTTP(rec, req)
|
||
|
||
if rec.Code >= http.StatusInternalServerError {
|
||
t.Fatalf("unexpected status %d, want < 500", rec.Code)
|
||
}
|
||
if bound.Content != "hello" {
|
||
t.Fatalf("Content = %q, want %q", bound.Content, "hello")
|
||
}
|
||
if bound.Images != nil {
|
||
t.Fatalf("Images = %v, want nil", bound.Images)
|
||
}
|
||
}
|