Files
zogo/httpx/httpx_test.go
4566704 e1c0cc1136 feat(httpx): 并入 go-hua/httpx 的 ParseXML 并补齐单元测试
- ParseXML(XML 请求体绑定, 空请求体返回 nil)为 zogo 版本唯一缺口,
  从 go-hua/httpx 并入; 顶层 README 同步补充说明
- 移植 go-hua 全套 httpx 测试(CodeMsg/OkJson/ErrorJson/四种绑定/Validate/
  ParseXML)并新增 Handle/HandleResult/空 body 用例; 修正 README 中
  wsc.New 笔误为 New
2026-09-20 12:37:11 +08:00

234 lines
6.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 自 go-hua/httpx 整体移植的单元测试(覆盖 OkJson/ErrorJson/Parse 绑定/ParseXML),
// 并补充 Handle/HandleResult 用例。
package httpx
import (
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
)
type bindReq struct {
Username string `form:"username" json:"username" uri:"username" header:"X-Username"`
Password string `form:"password" json:"password" uri:"password"`
}
func (r *bindReq) Validate() error {
if r.Username == "" {
return New(1001, "用户名不能为空")
}
return nil
}
func newTestContext(body string) (*gin.Context, *httptest.ResponseRecorder) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
if body != "" {
c.Request = httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
}
return c, w
}
func TestNewCodeMsg(t *testing.T) {
err := New(1001, "业务错误")
cm, ok := err.(*CodeMsg)
if !ok {
t.Fatalf("New 应返回 *CodeMsg, got %T", err)
}
if cm.Code != 1001 {
t.Errorf("Code = %d, want 1001", cm.Code)
}
if cm.Msg != "业务错误" {
t.Errorf("Msg = %q", cm.Msg)
}
if cm.Error() != "code: 1001, msg: 业务错误" {
t.Errorf("Error() = %q", cm.Error())
}
}
func TestOkJson(t *testing.T) {
c, w := newTestContext("")
OkJson(c, map[string]string{"k": "v"})
if !strings.Contains(w.Body.String(), `"code":0`) {
t.Errorf("OkJson 输出缺少 code:0: %s", w.Body.String())
}
if !strings.Contains(w.Body.String(), `"data"`) {
t.Errorf("OkJson 输出缺少 data: %s", w.Body.String())
}
}
func TestErrorJson(t *testing.T) {
c, w := newTestContext("")
ErrorJson(c, New(1001, "业务错误"))
// *CodeMsg 应使用其业务错误码与消息
if !strings.Contains(w.Body.String(), `"code":1001`) {
t.Errorf("ErrorJson 输出缺少 code:1001: %s", w.Body.String())
}
if !strings.Contains(w.Body.String(), `"msg":"业务错误"`) {
t.Errorf("ErrorJson msg 不符: %s", w.Body.String())
}
}
func TestErrorJsonPlainError(t *testing.T) {
c, w := newTestContext("")
ErrorJson(c, errors.New("数据库连接失败"))
// 非 *CodeMsg 的 error 统一 code=1,msg 为 err.Error()
if !strings.Contains(w.Body.String(), `"code":1`) {
t.Errorf("普通错误 code 应为 1: %s", w.Body.String())
}
if !strings.Contains(w.Body.String(), `"msg":"数据库连接失败"`) {
t.Errorf("普通错误 msg 不符: %s", w.Body.String())
}
}
func TestParseQueryBinding(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodGet, "/?username=admin&password=123", nil)
req := &bindReq{}
if err := Parse(c, req); err != nil {
t.Fatalf("Parse: %v", err)
}
if req.Username != "admin" || req.Password != "123" {
t.Errorf("query 绑定结果 = %+v", req)
}
}
func TestParseJsonBinding(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{"username":"admin","password":"123"}`))
c.Request.Header.Set("Content-Type", "application/json")
req := &bindReq{}
if err := Parse(c, req); err != nil {
t.Fatalf("Parse: %v", err)
}
if req.Username != "admin" || req.Password != "123" {
t.Errorf("json 绑定结果 = %+v", req)
}
}
func TestParseHeaderBinding(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
c.Request.Header.Set("X-Username", "admin")
req := &bindReq{}
if err := Parse(c, req); err != nil {
t.Fatalf("Parse: %v", err)
}
if req.Username != "admin" {
t.Errorf("header 绑定结果 = %+v", req)
}
}
func TestParseUriBinding(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodGet, "/1/admin", nil)
// Parse 仅当存在 id 参数时才执行 uri 绑定
c.Params = gin.Params{{Key: "id", Value: "1"}, {Key: "username", Value: "admin"}, {Key: "password", Value: "123"}}
req := &bindReq{}
if err := Parse(c, req); err != nil {
t.Fatalf("Parse: %v", err)
}
if req.Username != "admin" || req.Password != "123" {
t.Errorf("uri 绑定结果 = %+v", req)
}
}
func TestParseValidateFail(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
req := &bindReq{}
err := Parse(c, req)
if err == nil {
t.Fatal("Validate 失败应返回错误")
}
if cm, ok := err.(*CodeMsg); !ok || cm.Code != 1001 {
t.Errorf("错误类型应为 *CodeMsg(1001), got %T %v", err, err)
}
}
func TestParseXML(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`<bindReq><Username>admin</Username></bindReq>`))
req := &bindReq{}
if err := ParseXML(c, req); err != nil {
t.Fatalf("ParseXML: %v", err)
}
if req.Username != "admin" {
t.Errorf("xml 绑定结果 = %+v", req)
}
}
func TestParseXMLEmptyBody(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodPost, "/", nil)
req := &bindReq{}
if err := ParseXML(c, req); err != nil {
t.Fatalf("空请求体 ParseXML 应返回 nil: %v", err)
}
if req.Username != "" {
t.Errorf("空请求体不应绑定出值: %+v", req)
}
}
func TestHandle(t *testing.T) {
c, w := newTestContext(`{"username":"admin","password":"123"}`)
c.Request.Header.Set("Content-Type", "application/json")
Handle(c, func(req *bindReq) (*map[string]string, error) {
return &map[string]string{"user": req.Username}, nil
})
if !strings.Contains(w.Body.String(), `"user":"admin"`) {
t.Errorf("Handle 输出缺 data: %s", w.Body.String())
}
}
func TestHandleBizError(t *testing.T) {
c, w := newTestContext(`{"username":"admin","password":"123"}`)
c.Request.Header.Set("Content-Type", "application/json")
Handle(c, func(req *bindReq) (*map[string]string, error) {
return nil, New(1002, "被拦了")
})
if !strings.Contains(w.Body.String(), `"code":1002`) {
t.Errorf("Handle 业务错误应透传 CodeMsg: %s", w.Body.String())
}
}
func TestHandleResult(t *testing.T) {
c, w := newTestContext("")
HandleResult(c, map[string]string{"k": "v"}, nil)
if !strings.Contains(w.Body.String(), `"k":"v"`) {
t.Errorf("HandleResult 成功输出不符: %s", w.Body.String())
}
c, w = newTestContext("")
HandleResult(c, nil, New(1003, "失败了"))
if !strings.Contains(w.Body.String(), `"code":1003`) {
t.Errorf("HandleResult 错误输出不符: %s", w.Body.String())
}
}