// 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) } }