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
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
// 自 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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user