// 自 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(`admin`)) 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()) } }