188 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package routes
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
func newCommonRoutesTestRouter() *gin.Engine {
gin.SetMode(gin.TestMode)
r := gin.New()
RegisterCommonRoutes(r, service.NewHealthService(nil, nil))
return r
}
func newTestRouter(t *testing.T, hs *service.HealthService) *gin.Engine {
t.Helper()
gin.SetMode(gin.TestMode)
r := gin.New()
RegisterCommonRoutes(r, hs)
return r
}
func TestCommonRoutes_LivenessEndpoints(t *testing.T) {
r := newTestRouter(t, service.NewHealthService(nil, nil))
for _, path := range []string{"/healthz", "/health"} {
req := httptest.NewRequest(http.MethodGet, path, nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code, "liveness path %s should be 200", path)
}
}
func TestCommonRoutes_ReadyEndpoint_NoDepsReturnsOK(t *testing.T) {
// 没有 DB/Redis 依赖时 readiness 视为 ok早期启动场景
r := newTestRouter(t, service.NewHealthService(nil, nil))
req := httptest.NewRequest(http.MethodGet, "/ready", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
require.Contains(t, w.Body.String(), "\"ok\":true")
}
func TestCommonRoutes_SetupStatusUnchanged(t *testing.T) {
// 验证我们没有破坏既有的 /setup/status 行为(前端依赖)。
r := newTestRouter(t, service.NewHealthService(nil, nil))
req := httptest.NewRequest(http.MethodGet, "/setup/status", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
require.Contains(t, w.Body.String(), "needs_setup")
}
func TestCommonRoutes_ClaudeCodeBootstrap(t *testing.T) {
r := newCommonRoutesTestRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/api/claude_cli/bootstrap", nil)
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", w.Code, http.StatusOK)
}
var body struct {
ClientData any `json:"client_data"`
AdditionalModelOptions []map[string]any `json:"additional_model_options"`
AdditionalModelCosts map[string]map[string]any `json:"additional_model_costs"`
}
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if body.ClientData != nil {
t.Fatalf("client_data = %#v, want nil", body.ClientData)
}
if body.AdditionalModelOptions == nil || len(body.AdditionalModelOptions) != 0 {
t.Fatalf("additional_model_options = %#v, want empty array", body.AdditionalModelOptions)
}
if body.AdditionalModelCosts == nil || len(body.AdditionalModelCosts) != 0 {
t.Fatalf("additional_model_costs = %#v, want empty object", body.AdditionalModelCosts)
}
}
func TestCommonRoutes_ClaudeCodePolicyLimits(t *testing.T) {
r := newCommonRoutesTestRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/api/claude_code/policy_limits", nil)
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", w.Code, http.StatusOK)
}
var body struct {
Restrictions map[string]struct {
Allowed bool `json:"allowed"`
} `json:"restrictions"`
}
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if body.Restrictions == nil || len(body.Restrictions) != 0 {
t.Fatalf("restrictions = %#v, want empty object", body.Restrictions)
}
}
func TestCommonRoutes_GrowthBookJSONRoutes(t *testing.T) {
r := newCommonRoutesTestRouter()
testCases := []struct {
name string
method string
path string
}{
{name: "features", method: http.MethodGet, path: "/api/features/sdk-zAZezfDKGoZuXXKe"},
{name: "eval", method: http.MethodPost, path: "/api/eval/sdk-zAZezfDKGoZuXXKe"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest(tc.method, tc.path, strings.NewReader(`{"attributes":{}}`))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", w.Code, http.StatusOK)
}
if got := w.Header().Get("x-sse-support"); got != "enabled" {
t.Fatalf("x-sse-support = %q, want %q", got, "enabled")
}
var body struct {
Features map[string]any `json:"features"`
DateUpdated string `json:"dateUpdated"`
}
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if body.Features == nil || len(body.Features) != 0 {
t.Fatalf("features = %#v, want empty object", body.Features)
}
if body.DateUpdated != claudeCodeGrowthBookDateUpdated {
t.Fatalf("dateUpdated = %q, want %q", body.DateUpdated, claudeCodeGrowthBookDateUpdated)
}
})
}
}
func TestCommonRoutes_GrowthBookSSERoutes(t *testing.T) {
r := newCommonRoutesTestRouter()
testPaths := []string{
"/sub/sdk-zAZezfDKGoZuXXKe",
"/sub/features/sdk-zAZezfDKGoZuXXKe",
}
for _, path := range testPaths {
t.Run(path, func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, path, nil)
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", w.Code, http.StatusOK)
}
if got := w.Header().Get("Content-Type"); !strings.HasPrefix(got, "text/event-stream") {
t.Fatalf("Content-Type = %q, want text/event-stream*", got)
}
body := w.Body.String()
if !strings.Contains(body, "event:features") {
t.Fatalf("SSE body missing features event: %q", body)
}
if !strings.Contains(body, `"dateUpdated":"`+claudeCodeGrowthBookDateUpdated+`"`) {
t.Fatalf("SSE body missing dateUpdated payload: %q", body)
}
})
}
}