This commit is contained in:
tamakyi
2026-09-08 10:10:44 +08:00
commit 2ed27dd55e
288 changed files with 67061 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
// 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)
}
}