package ginx_test import ( "encoding/json" "errors" "net/http/httptest" "testing" "code.yun.ink/pkg/responsex" respgin "code.yun.ink/pkg/responsex/ginx" "github.com/gin-gonic/gin" ) 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) 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"] == "" { t.Error("message should not be empty") } } 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"]) } }