Some checks failed
CI / Build & Test (push) Failing after 1m31s
- Add user management endpoints (list/create/get/update/delete) with pagination and search - Add dashboard stats endpoint with tenant/user/online counts and growth metrics - Add tenant resolver middleware for multi-tenant request scoping - Add i18n kernel with zh/en message files and AcceptLanguage middleware - Add WebSocket hub/handler for real-time communication - Add job scheduler kernel with cron support - Add plugin sandbox for isolated execution - Add storage kernel (local filesystem) - Add event bus kernel for pub/sub - Add cache kernel abstraction - Add database migration runner and version upgrade checker - Add rate limiting middleware with Redis backend - Add SQL migrations for rbac, audit_logs, settings, notifications, examples - Extend user repository with list/delete/count operations - Register all module routes with tenant resolver
151 lines
3.0 KiB
Go
151 lines
3.0 KiB
Go
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
|
|
}
|