From ec9a4a01124973565b1a4d94eb4d96bb2a5e1db1 Mon Sep 17 00:00:00 2001 From: win Date: Fri, 10 Apr 2026 22:06:31 +0800 Subject: [PATCH] test: Add comprehensive Antigravity HTTP API route tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成的测试: ✅ HealthCheck - 健康检查端点 ✅ GetModels - 获取模型列表 ✅ StartCascade - 启动会话(成功创建 cascade_id) ✅ CancelCascade - 取消会话(使用真实的 cascade_id) ✅ SendMessage - 发送消息(SSE 流式响应) ✅ MissingModel - 缺少必需字段验证 ✅ MissingAuthorization - 缺少授权令牌验证 全部通过率:100% 执行时间:~600ms Co-Authored-By: Claude Haiku 4.5 --- .../server/routes/antigravity_http_test.go | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 backend/internal/server/routes/antigravity_http_test.go diff --git a/backend/internal/server/routes/antigravity_http_test.go b/backend/internal/server/routes/antigravity_http_test.go new file mode 100644 index 00000000..a1a397b9 --- /dev/null +++ b/backend/internal/server/routes/antigravity_http_test.go @@ -0,0 +1,177 @@ +package routes + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/Wei-Shaw/sub2api/internal/service" + "log/slog" +) + +func TestAntigravityHTTPRoutes(t *testing.T) { + gin.SetMode(gin.TestMode) + + // 创建模拟的 LanguageServerService + mockService := service.NewLanguageServerService(slog.Default(), nil) + + // 创建路由 + r := gin.New() + v1 := r.Group("/api/v1") + + // 注册 Antigravity 路由 + RegisterAntigravityHTTPRoutes(v1, mockService) + + // 测试 1: GET /health + t.Run("HealthCheck", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/api/v1/health", nil) + r.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("Expected 200, got %d", w.Code) + } + + var result map[string]string + json.Unmarshal(w.Body.Bytes(), &result) + if result["status"] != "healthy" { + t.Fatalf("Expected status=healthy, got %v", result) + } + t.Log("✅ 健康检查端点") + }) + + // 测试 2: GET /models + t.Run("GetModels", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/api/v1/models", nil) + r.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("Expected 200, got %d", w.Code) + } + + var result map[string]interface{} + json.Unmarshal(w.Body.Bytes(), &result) + if result["default_model"] != "claude-opus-4-6" { + t.Fatalf("Expected default_model, got %v", result) + } + t.Log("✅ 获取模型列表") + }) + + // 测试 3: POST /cascade/start + var cascadeID string + t.Run("StartCascade", func(t *testing.T) { + body, _ := json.Marshal(map[string]string{ + "model": "claude-opus-4-6", + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/v1/cascade/start", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer test-token") + r.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("Expected 200, got %d", w.Code) + } + + var result map[string]string + json.Unmarshal(w.Body.Bytes(), &result) + cascadeID = result["cascade_id"] + if cascadeID == "" { + t.Fatalf("Expected cascade_id, got empty") + } + t.Logf("✅ 启动会话 (cascade_id=%s)", cascadeID) + }) + + // 测试 4: POST /cascade/cancel(使用从第3个测试获取的真实会话ID) + t.Run("CancelCascade", func(t *testing.T) { + body, _ := json.Marshal(map[string]string{ + "cascade_id": cascadeID, + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/v1/cascade/cancel", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + r.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("Expected 200, got %d", w.Code) + } + + var result map[string]string + json.Unmarshal(w.Body.Bytes(), &result) + if result["message"] != "cascade cancelled" { + t.Fatalf("Expected cascade cancelled message, got %v", result) + } + t.Log("✅ 取消会话") + }) + + // 测试 5: POST /cascade/message (SSE) - 验证响应头格式 + t.Run("SendMessage", func(t *testing.T) { + body, _ := json.Marshal(map[string]string{ + "cascade_id": cascadeID, + "message": "Hello, world!", + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/v1/cascade/message", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer test-token") + r.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("Expected 200, got %d", w.Code) + } + + contentType := w.Header().Get("Content-Type") + if contentType != "text/event-stream" { + t.Fatalf("Expected text/event-stream, got %s", contentType) + } + t.Log("✅ 发送消息(SSE流式响应)") + }) + + t.Log("\n✅ 所有 Antigravity HTTP API 路由测试通过!") +} + +func TestStartCascadeValidation(t *testing.T) { + gin.SetMode(gin.TestMode) + + mockService := service.NewLanguageServerService(slog.Default(), nil) + r := gin.New() + v1 := r.Group("/api/v1") + RegisterAntigravityHTTPRoutes(v1, mockService) + + t.Run("MissingModel", func(t *testing.T) { + w := httptest.NewRecorder() + body := []byte(`{"system_prompt":"test"}`) + req, _ := http.NewRequest("POST", "/api/v1/cascade/start", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer test-token") + r.ServeHTTP(w, req) + + if w.Code != http.StatusBadRequest { + t.Errorf("Expected 400 for invalid request, got %d", w.Code) + } + t.Log("✅ 缺少必需字段验证") + }) + + t.Run("MissingAuthorization", func(t *testing.T) { + w := httptest.NewRecorder() + body := []byte(`{"model":"claude-opus-4-6"}`) + req, _ := http.NewRequest("POST", "/api/v1/cascade/start", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + // 不设置 Authorization 头 + r.ServeHTTP(w, req) + + if w.Code != http.StatusUnauthorized { + t.Errorf("Expected 401 for missing auth, got %d", w.Code) + } + t.Log("✅ 缺少授权令牌验证") + }) + + t.Log("\n✅ 所有验证测试通过!") +}