package ginext import ( "bytes" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" ) func init() { gin.SetMode(gin.TestMode) } func newTestCtx() (*gin.Context, *httptest.ResponseRecorder) { rec := httptest.NewRecorder() c, _ := gin.CreateTestContext(rec) c.Request = httptest.NewRequest(http.MethodGet, "/", nil) return c, rec } func TestJSONResponse_Basics(t *testing.T) { r := JSON(http.StatusOK, map[string]any{"a": 1}) if !r.IsSuccess() { t.Fatalf("expected IsSuccess true for 200") } ir, ok := r.(InspectableHTTPResponse) if !ok { t.Fatalf("expected InspectableHTTPResponse") } if ir.Statuscode() != http.StatusOK { t.Fatalf("statuscode mismatch: %d", ir.Statuscode()) } if ir.ContentType() != "application/json" { t.Fatalf("expected content-type application/json, got %q", ir.ContentType()) } } func TestJSONResponse_Write(t *testing.T) { c, rec := newTestCtx() r := JSON(http.StatusCreated, map[string]any{"hello": "world"}) r.Write(c) if rec.Code != http.StatusCreated { t.Fatalf("expected 201, got %d", rec.Code) } if !bytes.Contains(rec.Body.Bytes(), []byte("\"hello\":\"world\"")) { t.Fatalf("unexpected body: %s", rec.Body.String()) } } func TestJSONResponse_BodyString(t *testing.T) { c, _ := newTestCtx() r := JSON(http.StatusOK, map[string]any{"x": 42}) ir := r.(InspectableHTTPResponse) body := ir.BodyString(c) if body == nil { t.Fatalf("expected body, got nil") } if !bytes.Contains([]byte(*body), []byte("\"x\":42")) { t.Fatalf("unexpected body: %q", *body) } } func TestJSONResponse_FilterFromContext(t *testing.T) { c, _ := newTestCtx() SetJSONFilter(c, "abc") r := JSON(http.StatusOK, map[string]any{"x": 1}) body := r.(InspectableHTTPResponse).BodyString(c) if body == nil { t.Fatalf("expected body") } } func TestJSONResponse_FilterOverride(t *testing.T) { c, _ := newTestCtx() SetJSONFilter(c, "abc") r := JSONWithFilter(http.StatusOK, map[string]any{"x": 1}, "override") if r == nil { t.Fatalf("expected response") } if !r.IsSuccess() { t.Fatalf("expected success") } body := r.(InspectableHTTPResponse).BodyString(c) if body == nil { t.Fatalf("expected body") } } func TestResponse_IsSuccessRanges(t *testing.T) { cases := []struct { code int ok bool }{ {100, false}, {199, false}, {200, true}, {201, true}, {299, true}, {300, true}, {399, true}, {400, false}, {500, false}, } for _, tc := range cases { r := JSON(tc.code, nil) if r.IsSuccess() != tc.ok { t.Errorf("status %d: expected IsSuccess=%v", tc.code, tc.ok) } r2 := Status(tc.code) if r2.IsSuccess() != tc.ok { t.Errorf("Status(%d): expected IsSuccess=%v", tc.code, tc.ok) } r3 := Text(tc.code, "x") if r3.IsSuccess() != tc.ok { t.Errorf("Text(%d): expected IsSuccess=%v", tc.code, tc.ok) } r4 := Data(tc.code, "text/plain", []byte("x")) if r4.IsSuccess() != tc.ok { t.Errorf("Data(%d): expected IsSuccess=%v", tc.code, tc.ok) } } } func TestResponse_WithHeader(t *testing.T) { r := JSON(http.StatusOK, nil). WithHeader("X-Foo", "bar"). WithHeader("X-Baz", "qux") headers := r.(InspectableHTTPResponse).Headers() if len(headers) != 2 { t.Fatalf("expected 2 headers, got %d", len(headers)) } if headers[0] != "X-Foo=bar" || headers[1] != "X-Baz=qux" { t.Fatalf("headers wrong: %v", headers) } } func TestResponse_WithCookie_DoesNotPanic(t *testing.T) { r := JSON(http.StatusOK, nil). WithCookie("session", "abc", 3600, "/", "example.com", true, true) if r == nil { t.Fatalf("expected response") } c, rec := newTestCtx() r.Write(c) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } cookies := rec.Result().Cookies() if len(cookies) != 1 { t.Fatalf("expected 1 cookie, got %d", len(cookies)) } if cookies[0].Name != "session" || cookies[0].Value != "abc" { t.Fatalf("cookie wrong: %+v", cookies[0]) } } func TestTextResponse(t *testing.T) { c, rec := newTestCtx() r := Text(http.StatusOK, "hello") if r.(InspectableHTTPResponse).ContentType() != "text/plain" { t.Fatalf("expected text/plain") } body := r.(InspectableHTTPResponse).BodyString(c) if body == nil || *body != "hello" { t.Fatalf("body mismatch") } r.Write(c) if rec.Body.String() != "hello" { t.Fatalf("write body mismatch: %q", rec.Body.String()) } } func TestDataResponse(t *testing.T) { c, rec := newTestCtx() payload := []byte{0x01, 0x02, 0x03} r := Data(http.StatusOK, "application/octet-stream", payload) if r.(InspectableHTTPResponse).ContentType() != "application/octet-stream" { t.Fatalf("contenttype mismatch") } r.Write(c) if !bytes.Equal(rec.Body.Bytes(), payload) { t.Fatalf("body mismatch") } body := r.(InspectableHTTPResponse).BodyString(c) if body == nil || *body != string(payload) { t.Fatalf("BodyString mismatch") } } func TestStatusResponse(t *testing.T) { c, rec := newTestCtx() r := Status(http.StatusNoContent) if r.(InspectableHTTPResponse).ContentType() != "" { t.Fatalf("expected empty content type") } if r.(InspectableHTTPResponse).BodyString(c) != nil { t.Fatalf("expected nil body") } r.Write(c) if c.Writer.Status() != http.StatusNoContent { t.Fatalf("expected 204, got %d", c.Writer.Status()) } _ = rec } func TestRedirectResponse(t *testing.T) { c, rec := newTestCtx() r := Redirect(http.StatusFound, "/elsewhere") if r.(InspectableHTTPResponse).Statuscode() != http.StatusFound { t.Fatalf("status mismatch") } if r.(InspectableHTTPResponse).ContentType() != "" { t.Fatalf("expected empty content type") } if r.(InspectableHTTPResponse).BodyString(c) != nil { t.Fatalf("expected nil body") } r.Write(c) if rec.Code != http.StatusFound { t.Fatalf("expected 302, got %d", rec.Code) } if rec.Header().Get("Location") != "/elsewhere" { t.Fatalf("expected Location header") } } func TestNotImplemented(t *testing.T) { r := NotImplemented() if r == nil { t.Fatalf("expected response") } if r.IsSuccess() { t.Fatalf("NotImplemented must not be success") } } func TestError_NotSuccess(t *testing.T) { r := Error(http.ErrAbortHandler) if r.IsSuccess() { t.Fatalf("error response must not be success") } herr, ok := r.(HTTPErrorResponse) if !ok { t.Fatalf("expected HTTPErrorResponse") } if herr.Error() == nil { t.Fatalf("expected non-nil err") } } func TestError_ContentTypeJSON(t *testing.T) { r := Error(http.ErrAbortHandler) if r.(InspectableHTTPResponse).ContentType() != "application/json" { t.Fatalf("expected application/json") } } func TestDownloadData(t *testing.T) { c, rec := newTestCtx() payload := []byte("file content") r := DownloadData(http.StatusOK, "text/plain", "f.txt", payload) if r.(InspectableHTTPResponse).ContentType() != "text/plain" { t.Fatalf("contenttype mismatch") } if r.(InspectableHTTPResponse).Statuscode() != http.StatusOK { t.Fatalf("status mismatch") } body := r.(InspectableHTTPResponse).BodyString(c) if body == nil || *body != string(payload) { t.Fatalf("body mismatch") } r.Write(c) if rec.Header().Get("Content-Disposition") == "" { t.Fatalf("expected Content-Disposition header") } if !bytes.Contains([]byte(rec.Header().Get("Content-Disposition")), []byte("f.txt")) { t.Fatalf("Content-Disposition does not contain filename: %q", rec.Header().Get("Content-Disposition")) } } func TestSeekable(t *testing.T) { r := Seekable("foo.bin", "application/octet-stream", bytes.NewReader([]byte("xyz"))) if !r.IsSuccess() { t.Fatalf("seekable must be success") } ir := r.(InspectableHTTPResponse) if ir.Statuscode() != 200 { t.Fatalf("expected 200") } if ir.ContentType() != "application/octet-stream" { t.Fatalf("contenttype mismatch") } body := ir.BodyString(nil) if body == nil || *body != "(seekable)" { t.Fatalf("BodyString mismatch") } } func TestFile_Builders(t *testing.T) { r := File("text/plain", "/tmp/this-file-should-not-exist-xyz") if !r.IsSuccess() { t.Fatalf("File must IsSuccess true") } if r.(InspectableHTTPResponse).Statuscode() != 200 { t.Fatalf("expected 200") } if r.(InspectableHTTPResponse).ContentType() != "text/plain" { t.Fatalf("contenttype mismatch") } r2 := Download("application/pdf", "/tmp/this-file-should-not-exist-xyz", "doc.pdf") if r2.(InspectableHTTPResponse).ContentType() != "application/pdf" { t.Fatalf("contenttype mismatch") } body := r.(InspectableHTTPResponse).BodyString(nil) if body != nil { t.Fatalf("expected nil body for nonexistent file") } }