package testutil import ( "bytes" "encoding/json" "io" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" ) func init() { gin.SetMode(gin.TestMode) } type APITest struct { t *testing.T engine *gin.Engine } func NewAPITest(t *testing.T, engine *gin.Engine) *APITest { t.Helper() return &APITest{t: t, engine: engine} } func (a *APITest) Request(method, path string, body interface{}) *APIRequest { t := a.t t.Helper() var reader io.Reader if body != nil { b, err := json.Marshal(body) if err != nil { t.Fatalf("marshal request body: %v", err) } reader = bytes.NewReader(b) } req := httptest.NewRequest(method, path, reader) if body != nil { req.Header.Set("Content-Type", "application/json") } return &APIRequest{t: t, engine: a.engine, req: req} } func (a *APITest) Get(path string) *APIRequest { return a.Request("GET", path, nil) } func (a *APITest) Post(path string, body interface{}) *APIRequest { return a.Request("POST", path, body) } func (a *APITest) Put(path string, body interface{}) *APIRequest { return a.Request("PUT", path, body) } func (a *APITest) Delete(path string) *APIRequest { return a.Request("DELETE", path, nil) } type APIRequest struct { t *testing.T engine *gin.Engine req *http.Request } func (r *APIRequest) WithToken(token string) *APIRequest { r.req.Header.Set("Authorization", "Bearer "+token) return r } func (r *APIRequest) WithTenant(tenantID string) *APIRequest { r.req.Header.Set("X-Tenant-ID", tenantID) return r } func (r *APIRequest) WithHeader(key, value string) *APIRequest { r.req.Header.Set(key, value) return r } func (r *APIRequest) Do() *APIResponse { r.t.Helper() w := httptest.NewRecorder() r.engine.ServeHTTP(w, r.req) resp := &APIResponse{ Code: w.Code, Headers: w.Header(), BodyBytes: w.Body.Bytes(), t: r.t, } var result map[string]interface{} if err := json.Unmarshal(w.Body.Bytes(), &result); err == nil { resp.JSON = result } return resp } type APIResponse struct { Code int Headers http.Header BodyBytes []byte JSON map[string]interface{} t *testing.T } func (r *APIResponse) AssertStatus(expected int) *APIResponse { r.t.Helper() if r.Code != expected { r.t.Errorf("expected status %d, got %d; body: %s", expected, r.Code, string(r.BodyBytes)) } return r } func (r *APIResponse) AssertCode(expected float64) *APIResponse { r.t.Helper() if r.JSON == nil { r.t.Fatal("response is not JSON") } got, ok := r.JSON["code"] if !ok { r.t.Fatal("response has no 'code' field") } if got.(float64) != expected { r.t.Errorf("expected code %.0f, got %.0f; body: %s", expected, got, string(r.BodyBytes)) } return r } func (r *APIResponse) Data() map[string]interface{} { r.t.Helper() if r.JSON == nil { r.t.Fatal("response is not JSON") } data, ok := r.JSON["data"] if !ok { r.t.Fatal("response has no 'data' field") } m, ok := data.(map[string]interface{}) if !ok { r.t.Fatalf("data is not an object: %T", data) } return m }