package response_test import ( "encoding/json" "net/http/httptest" "strings" "testing" "github.com/EthanCodeCraft/xlgo-core/response" "github.com/gin-gonic/gin" ) func setupTestRouter() *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() return r } func TestSuccess(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.Success(c, gin.H{"message": "ok"}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("Success status = %d", w.Code) } // 验证响应体包含 code=0 body := w.Body.String() if !contains(body, `"code":0`) { t.Errorf("Success body should contain code:0, got %s", body) } } func TestSuccessWithMsg(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.SuccessWithMsg(c, "操作成功", gin.H{"id": 1}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("SuccessWithMsg status = %d", w.Code) } } func TestFail(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.Fail(c, "参数错误") }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("Fail status = %d", w.Code) } body := w.Body.String() if !contains(body, `"code":1`) { t.Errorf("Fail body should contain code:1, got %s", body) } } func TestFailWithCode(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.FailWithCode(c, response.CodeUnauthorized, "未授权") }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("FailWithCode status = %d", w.Code) } } func TestFailWithErrorNilDoesNotPanic_M6(t *testing.T) { r := setupTestRouter() r.Use(func(c *gin.Context) { c.Set("request_id", "req-m6") c.Next() }) r.GET("/test", func(c *gin.Context) { response.FailWithError(c, nil) }) w := httptest.NewRecorder() r.ServeHTTP(w, httptest.NewRequest("GET", "/test", nil)) if w.Code != 200 { t.Fatalf("FailWithError(nil) status = %d, want 200 in business mode", w.Code) } var body response.Response if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil { t.Fatalf("decode response: %v", err) } if body.Code != response.CodeServerError { t.Fatalf("code = %d, want %d", body.Code, response.CodeServerError) } if body.RequestID != "req-m6" { t.Fatalf("request_id = %q, want req-m6", body.RequestID) } } func TestUnauthorized(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.Unauthorized(c, "请先登录") }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("Unauthorized status = %d", w.Code) } } func TestNotFound(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.NotFound(c, "资源不存在") }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("NotFound status = %d", w.Code) } } func TestServerError(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.ServerError(c, "服务器错误") }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("ServerError status = %d", w.Code) } } func TestRateLimit(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.RateLimit(c) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("RateLimit status = %d", w.Code) } body := w.Body.String() if !contains(body, `"code":`) { t.Errorf("RateLimit body should contain code, got %s", body) } } func TestPage(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { items := []string{"a", "b", "c"} response.Page(c, items, 100, 1, 20) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("Page status = %d", w.Code) } body := w.Body.String() if !contains(body, `"total":100`) { t.Errorf("Page body should contain total:100, got %s", body) } } func TestDownload(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { data := []byte("test file content") response.Download(c, "test.txt", data) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("Download status = %d", w.Code) } // 验证响应头 contentType := w.Header().Get("Content-Type") if contentType != "application/octet-stream" { t.Errorf("Download Content-Type = %s, want application/octet-stream", contentType) } disposition := w.Header().Get("Content-Disposition") if !contains(disposition, "test.txt") { t.Errorf("Download Content-Disposition should contain filename, got %s", disposition) } } // TestDownloadChineseFilename_M6:中文文件名经 RFC 5987 编码(filename*),不乱码。 func TestDownloadChineseFilename_M6(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.Download(c, "报告.txt", []byte("x")) }) w := httptest.NewRecorder() r.ServeHTTP(w, httptest.NewRequest("GET", "/test", nil)) disposition := w.Header().Get("Content-Disposition") // 应含 RFC 5987 编码的 filename*(百分号编码),且无原始 UTF-8 字节直接拼入 filename= 段 if !contains(disposition, "filename*=UTF-8''") { t.Fatalf("Content-Disposition should contain filename*=UTF-8''..., got %s", disposition) } if !contains(disposition, "%E6%8A%A5%E5%91%8A") { // "报告" 的百分号编码 t.Fatalf("Content-Disposition should contain percent-encoded 报告, got %s", disposition) } // ASCII 回退名不应为空 if !contains(disposition, `filename="`) { t.Fatalf("Content-Disposition should contain ASCII fallback filename, got %s", disposition) } } func TestDownloadWithContentType(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { data := []byte("test file content") response.DownloadWithContentType(c, "test.pdf", "application/pdf", data) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("DownloadWithContentType status = %d", w.Code) } contentType := w.Header().Get("Content-Type") if contentType != "application/pdf" { t.Errorf("DownloadWithContentType = %s, want application/pdf", contentType) } } func TestDownloadReaderStreamsContent_M6(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.DownloadReader(c, "big.txt", "text/plain", 11, strings.NewReader("hello world")) }) w := httptest.NewRecorder() r.ServeHTTP(w, httptest.NewRequest("GET", "/test", nil)) if w.Code != 200 { t.Fatalf("DownloadReader status = %d, want 200", w.Code) } if got := w.Header().Get("Content-Type"); !contains(got, "text/plain") { t.Fatalf("Content-Type = %q, want text/plain", got) } if got := w.Header().Get("Content-Length"); got != "11" { t.Fatalf("Content-Length = %q, want 11", got) } if got := w.Body.String(); got != "hello world" { t.Fatalf("body = %q, want hello world", got) } } func TestHTML(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.HTML(c, "Hello") }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 200 { t.Errorf("HTML status = %d", w.Code) } contentType := w.Header().Get("Content-Type") if !contains(contentType, "text/html") { t.Errorf("HTML Content-Type should be text/html, got %s", contentType) } } func TestRedirect(t *testing.T) { r := setupTestRouter() r.GET("/test", func(c *gin.Context) { response.Redirect(c, 302, "/new-location") }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) // 重定向返回 302 if w.Code != 302 { t.Errorf("Redirect status = %d, want 302", w.Code) } location := w.Header().Get("Location") if location != "/new-location" { t.Errorf("Redirect Location = %s, want /new-location", location) } } func TestErrorCodes(t *testing.T) { // 测试错误码定义 if response.CodeSuccess != 0 { t.Errorf("CodeSuccess = %d, want 0", response.CodeSuccess) } if response.CodeFail != 1 { t.Errorf("CodeFail = %d, want 1", response.CodeFail) } if response.CodeUnauthorized == 0 { t.Error("CodeUnauthorized should not be 0") } if response.CodeNotFound == 0 { t.Error("CodeNotFound should not be 0") } if response.CodeServerError == 0 { t.Error("CodeServerError should not be 0") } if response.CodeRateLimit == 0 { t.Error("CodeRateLimit should not be 0") } } func TestResponseStructure(t *testing.T) { // 测试 Response 结构体 resp := response.Response{ Code: 0, Msg: "成功", Data: gin.H{"id": 1}, RequestID: "test-123", } if resp.Code != 0 { t.Error("Response Code failed") } if resp.Msg != "成功" { t.Error("Response Msg failed") } } func TestPageDataStructure(t *testing.T) { // 测试 PageData 结构体 data := response.PageData{ Items: []string{"a", "b"}, Total: 100, Page: 1, PageSize: 20, } if data.Total != 100 { t.Error("PageData Total failed") } } func contains(s, sub string) bool { for i := 0; i <= len(s)-len(sub); i++ { if s[i:i+len(sub)] == sub { return true } } return false }