package resp import ( "encoding/json" "errors" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" ) func setupRouter() *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() return r } func decode(t *testing.T, w *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any if err := json.Unmarshal(w.Body.Bytes(), &m); err != nil { t.Fatalf("response not json: %v (%s)", err, w.Body.String()) } return m } func TestFailEnvelope(t *testing.T) { r := setupRouter() r.GET("/x", func(c *gin.Context) { BadRequest(c, "参数不合法") }) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/x", nil) r.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("status = %d, want 400", w.Code) } m := decode(t, w) if m["detail"] != "参数不合法" { t.Fatalf("detail = %v", m["detail"]) } } func TestInternalErrSanitizes(t *testing.T) { r := setupRouter() dbErr := errors.New("SQL logic error: no such table: users (database is locked)") r.GET("/x", func(c *gin.Context) { InternalErr(c, dbErr) }) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/x", nil) r.ServeHTTP(w, req) if w.Code != http.StatusInternalServerError { t.Fatalf("status = %d, want 500", w.Code) } m := decode(t, w) // 客户端不得看到 DB 错误原文 if m["detail"] == dbErr.Error() { t.Fatalf("internal error leaked to client: %v", m["detail"]) } if m["code"] != "internal" { t.Fatalf("code = %v, want internal", m["code"]) } } func TestPageEnvelope(t *testing.T) { r := setupRouter() r.GET("/x", func(c *gin.Context) { Page(c, []string{"a", "b"}, 42, 2, 10) }) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/x", nil) r.ServeHTTP(w, req) m := decode(t, w) if m["total"].(float64) != 42 || m["page"].(float64) != 2 || m["page_size"].(float64) != 10 { t.Fatalf("pagination envelope wrong: %v", m) } items, ok := m["items"].([]any) if !ok || len(items) != 2 { t.Fatalf("items wrong: %v", m["items"]) } }