Files
responsex/ginx/gin_test.go
T
2026-09-19 21:41:50 +08:00

356 lines
7.9 KiB
Go
Executable File
Raw 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.
package ginx_test
import (
"context"
"encoding/json"
"errors"
"net/http/httptest"
"testing"
"code.yun.ink/pkg/responsex"
respgin "code.yun.ink/pkg/responsex/ginx"
"github.com/gin-gonic/gin"
"github.com/yuninks/langx"
)
func init() {
gin.SetMode(gin.TestMode)
}
func decodeBody(t *testing.T, w *httptest.ResponseRecorder) map[string]any {
t.Helper()
var body map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatalf("decode body failed: %v, raw: %s", err, w.Body.String())
}
return body
}
func newGinCtx(w *httptest.ResponseRecorder) *gin.Context {
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest("GET", "/", nil)
return c
}
// ---------- Success ----------
func TestSuccess(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
// c.Set("trace_id","adada")
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), "trace_id", "adada"))
respgin.Success(c, "hello world")
body := decodeBody(t, w)
if body["code"] != float64(200) {
t.Errorf("code = %v, want 200", body["code"])
}
if body["data"] != "hello world" {
t.Errorf("data = %v, want hello world", body["data"])
}
}
func TestSuccessWithOptions(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.Success(c, "created",
responsex.SetHttpStatusCode(201),
responsex.SetHeader("X-Custom", "gin"),
)
if w.Code != 201 {
t.Errorf("http status = %d, want 201", w.Code)
}
if v := w.Header().Get("X-Custom"); v != "gin" {
t.Errorf("X-Custom = %q, want gin", v)
}
}
func TestSuccessNilData(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.Success(c, nil)
body := decodeBody(t, w)
if _, ok := body["data"]; ok {
t.Errorf("data should be omitted for nil")
}
}
// ---------- Error ----------
func TestError(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.Error(c, errors.New("gin error"))
body := decodeBody(t, w)
if body["code"] != float64(400) {
t.Errorf("code = %v, want 400", body["code"])
}
if body["message"] != "gin error" {
t.Errorf("message = %v, want 'gin error'", body["message"])
}
}
func TestErrorWithHttpStatus(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.Error(c, errors.New("server error"),
responsex.SetHttpStatusCode(500),
)
if w.Code != 500 {
t.Errorf("http status = %d, want 500", w.Code)
}
}
func TestErrorWithHeader(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.Error(c, errors.New("bad request"),
responsex.SetHttpStatusCode(400),
responsex.SetHeaders(map[string]string{
"X-Error-Code": "GIN_001",
"X-Request-Id": "abc",
}),
)
if v := w.Header().Get("X-Error-Code"); v != "GIN_001" {
t.Errorf("X-Error-Code = %q, want GIN_001", v)
}
if v := w.Header().Get("X-Request-Id"); v != "abc" {
t.Errorf("X-Request-Id = %q, want abc", v)
}
}
// ---------- ErrorWithData ----------
func TestErrorWithData(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.ErrorWithData(c, errors.New("validation error"), map[string]any{
"fields": []string{"email", "name"},
})
body := decodeBody(t, w)
data, ok := body["data"].(map[string]any)
if !ok {
t.Fatalf("data should be map, got %T", body["data"])
}
fields, _ := data["fields"].([]any)
if len(fields) != 2 {
t.Errorf("fields length = %d, want 2", len(fields))
}
}
func TestErrorWithDataAndOptions(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.ErrorWithData(c, errors.New("gone"), "resource",
responsex.SetHttpStatusCode(410),
)
if w.Code != 410 {
t.Errorf("http status = %d, want 410", w.Code)
}
}
// ---------- ErrorStr ----------
func TestErrorStr(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.ErrorStr(c, "string error")
body := decodeBody(t, w)
if body["message"] != "string error" {
t.Errorf("message = %v, want 'string error'", body["message"])
}
}
func TestErrorStrWithOptions(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.ErrorStr(c, "forbidden",
responsex.SetHttpStatusCode(403),
responsex.SetHeader("X-Reason", "no-access"),
)
if w.Code != 403 {
t.Errorf("http status = %d, want 403", w.Code)
}
}
// ---------- SuccessWithPage ----------
func TestSuccessWithPage(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
page := responsex.Pagination{
Page: 1,
Size: 10,
TotalPage: 3,
TotalCount: 30,
}
respgin.SuccessWithPage(c, []string{"a", "b"}, page)
body := decodeBody(t, w)
p, ok := body["pagination"].(map[string]any)
if !ok {
t.Fatalf("pagination should be map, got %T", body["pagination"])
}
if p["total_count"] != float64(30) {
t.Errorf("total_count = %v, want 30", p["total_count"])
}
}
func TestSuccessWithPageAndOptions(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
page := responsex.Pagination{Page: 1, Size: 20, TotalPage: 1, TotalCount: 20}
respgin.SuccessWithPage(c, []int{1}, page,
responsex.SetHeader("X-Total", "20"),
)
if v := w.Header().Get("X-Total"); v != "20" {
t.Errorf("X-Total = %q, want 20", v)
}
}
// ---------- MsgMapData / MsgKV ----------
func TestMsgKV(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.MsgKV(c, "welcome", "name", "Alice")
// 仅验证 HTTP 200 正常返回,具体内容由 langx 决定
if w.Code != 200 {
t.Errorf("http status = %d, want 200", w.Code)
}
}
func TestMsgKVWithOptions(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.MsgKV(c, "welcome", "name", "Alice",
responsex.SetHeader("X-Lang", "zh"),
)
if v := w.Header().Get("X-Lang"); v != "zh" {
t.Errorf("X-Lang = %q, want zh", v)
}
}
// ---------- Custom ----------
func TestCustom(t *testing.T) {
w := httptest.NewRecorder()
c := newGinCtx(w)
respgin.Custom(c,
responsex.SetCode(100),
responsex.SetMessage("custom gin"),
responsex.SetHttpStatusCode(202),
)
if w.Code != 202 {
t.Errorf("http status = %d, want 202", w.Code)
}
body := decodeBody(t, w)
if body["code"] != float64(100) {
t.Errorf("code = %v, want 100", body["code"])
}
}
// ---------- Middleware:trace_id 桥接 ----------
func newEngine() *gin.Engine {
r := gin.New()
r.Use(respgin.Middleware())
return r
}
func TestMiddlewareBridgesTraceID(t *testing.T) {
r := newEngine()
r.GET("/t", func(c *gin.Context) {
c.Set("trace_id", "trace-from-gin")
respgin.Success(c, "ok")
})
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest("GET", "/t", nil))
body := decodeBody(t, w)
if body["trace_id"] != "trace-from-gin" {
t.Errorf("trace_id = %v, want trace-from-gin (需要 Middleware 桥接)", body["trace_id"])
}
}
func TestMiddlewareTraceIDFromHeader(t *testing.T) {
r := newEngine()
r.GET("/t", func(c *gin.Context) {
respgin.Success(c, "ok")
})
req := httptest.NewRequest("GET", "/t", nil)
req.Header.Set("X-Request-Id", "req-header-id")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
body := decodeBody(t, w)
if body["trace_id"] != "req-header-id" {
t.Errorf("trace_id = %v, want req-header-id", body["trace_id"])
}
}
// ---------- Middleware:按 Accept-Language 翻译 ----------
func TestMiddlewareBridgesLanguage(t *testing.T) {
langx.AppendTrans("en", map[string]string{
"responsex_success": "success",
"not_found": "resource not found",
})
r := newEngine()
r.GET("/t", func(c *gin.Context) {
respgin.Success(c, "ok")
})
r.GET("/e", func(c *gin.Context) {
respgin.ErrorStr(c, "not_found")
})
req := httptest.NewRequest("GET", "/t", nil)
req.Header.Set("Accept-Language", "en")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if body := decodeBody(t, w); body["message"] != "success" {
t.Errorf("message = %v, want success", body["message"])
}
req = httptest.NewRequest("GET", "/e", nil)
req.Header.Set("Accept-Language", "en")
w = httptest.NewRecorder()
r.ServeHTTP(w, req)
if body := decodeBody(t, w); body["message"] != "resource not found" {
t.Errorf("message = %v, want 'resource not found'", body["message"])
}
}