Some checks failed
Security Scan / backend-security (push) Failing after 3s
Security Scan / frontend-security (push) Failing after 5s
CI / test (push) Failing after 3s
CI / frontend (push) Failing after 3s
CI / golangci-lint (push) Failing after 3s
CI / windsurf-platform (macos-latest) (push) Has been cancelled
CI / windsurf-platform (windows-latest) (push) Has been cancelled
189 lines
4.9 KiB
Go
189 lines
4.9 KiB
Go
package service
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"testing"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// TestHTTPResponseFlow 测试完整的 HTTP 请求-响应流,看客户端会收到什么
|
||
func TestHTTPResponseFlow(t *testing.T) {
|
||
t.Log("🔥 模拟完整的 HTTP 请求-响应流...")
|
||
t.Log("")
|
||
|
||
// 创建一个模拟的服务
|
||
gin.SetMode(gin.TestMode)
|
||
router := gin.New()
|
||
|
||
// 模拟账号测试端点
|
||
router.POST("/api/v1/admin/accounts/:id/test", func(c *gin.Context) {
|
||
// 模拟返回错误的情况
|
||
|
||
// 设置 SSE 头
|
||
c.Header("Content-Type", "text/event-stream")
|
||
c.Header("Cache-Control", "no-cache")
|
||
c.Header("Connection", "keep-alive")
|
||
c.Header("X-Accel-Buffering", "no")
|
||
c.Status(http.StatusOK)
|
||
|
||
// 发送测试开始事件
|
||
event1 := map[string]interface{}{
|
||
"type": "test_start",
|
||
"model": "claude-opus-4-6",
|
||
}
|
||
jsonData1, _ := json.Marshal(event1)
|
||
c.Writer.WriteString("data: " + string(jsonData1) + "\n\n")
|
||
c.Writer.Flush()
|
||
|
||
// 模拟一个错误:比如 "INVALID_TOKEN" 或其他上游错误
|
||
// 这里我们故意测试不同的错误信息来看 curl 会显示什么
|
||
|
||
errorMessages := []string{
|
||
"INVALID_TOKEN",
|
||
"INTERNAL_ERROR",
|
||
"Invalid authentication credentials",
|
||
"Th", // 测试短错误
|
||
"IT", // 直接测试 "IT"
|
||
}
|
||
|
||
selectedError := errorMessages[3] // 选择第 4 个:这应该显示为 "Th" 而不是 "IT"
|
||
|
||
event2 := map[string]interface{}{
|
||
"type": "error",
|
||
"error": selectedError,
|
||
"success": false,
|
||
}
|
||
jsonData2, _ := json.Marshal(event2)
|
||
c.Writer.WriteString("data: " + string(jsonData2) + "\n\n")
|
||
c.Writer.Flush()
|
||
|
||
// 发送完成事件
|
||
event3 := map[string]interface{}{
|
||
"type": "test_complete",
|
||
"success": false,
|
||
}
|
||
jsonData3, _ := json.Marshal(event3)
|
||
c.Writer.WriteString("data: " + string(jsonData3) + "\n\n")
|
||
c.Writer.Flush()
|
||
|
||
t.Logf("📤 服务器发送的错误: '%s'", selectedError)
|
||
})
|
||
|
||
// 测试 1: 发送 HTTP 请求
|
||
t.Run("SendRequestAndCheckResponse", func(t *testing.T) {
|
||
t.Log("步骤 1: 发送 HTTP 请求...")
|
||
|
||
req := httptest.NewRequest("POST", "/api/v1/admin/accounts/68/test",
|
||
bytes.NewReader([]byte(`{"model_id":"claude-opus-4-6"}`)))
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
w := httptest.NewRecorder()
|
||
router.ServeHTTP(w, req)
|
||
|
||
t.Log("✅ 请求已发送")
|
||
t.Log("")
|
||
|
||
// 步骤 2: 检查响应
|
||
t.Log("步骤 2: 分析 HTTP 响应...")
|
||
t.Logf(" HTTP Status: %d", w.Code)
|
||
t.Logf(" Content-Type: %s", w.Header().Get("Content-Type"))
|
||
t.Log("")
|
||
|
||
// 步骤 3: 读取 SSE 响应
|
||
t.Log("步骤 3: 读取 SSE 事件...")
|
||
body := w.Body.String()
|
||
t.Logf(" 响应总长度: %d 字节", len(body))
|
||
t.Log("")
|
||
|
||
// 解析 SSE 事件
|
||
lines := bytes.Split([]byte(body), []byte("\n\n"))
|
||
for i, line := range lines {
|
||
if len(line) == 0 {
|
||
continue
|
||
}
|
||
|
||
// 去掉 "data: " 前缀
|
||
if bytes.HasPrefix(line, []byte("data: ")) {
|
||
data := bytes.TrimPrefix(line, []byte("data: "))
|
||
|
||
var event map[string]interface{}
|
||
err := json.Unmarshal(data, &event)
|
||
if err != nil {
|
||
t.Logf(" 事件 %d: [解析失败] %v", i, err)
|
||
continue
|
||
}
|
||
|
||
t.Logf(" 事件 %d:", i)
|
||
t.Logf(" type: %v", event["type"])
|
||
|
||
if errMsg, ok := event["error"]; ok {
|
||
t.Logf(" error: %v (长度: %d)", errMsg, len(errMsg.(string)))
|
||
|
||
// 这就是 curl 会看到的错误信息
|
||
errStr := errMsg.(string)
|
||
if errStr == "IT" {
|
||
t.Logf(" ✓ 发现 'IT' 错误!")
|
||
} else if errStr == "Th" {
|
||
t.Logf(" ℹ️ 这是 'Th' 而不是 'IT'")
|
||
} else {
|
||
t.Logf(" ℹ️ 实际错误: '%s'", errStr)
|
||
}
|
||
}
|
||
|
||
if model, ok := event["model"]; ok {
|
||
t.Logf(" model: %v", model)
|
||
}
|
||
}
|
||
}
|
||
|
||
t.Log("")
|
||
t.Log("📋 完整的原始响应:")
|
||
t.Logf("%s", body)
|
||
})
|
||
|
||
// 测试 2: 模拟真实的 curl 请求
|
||
t.Run("SimulateRealCurlRequest", func(t *testing.T) {
|
||
t.Log("步骤: 模拟真实 curl 命令...")
|
||
t.Log("")
|
||
|
||
// 发送请求
|
||
req := httptest.NewRequest("POST", "/api/v1/admin/accounts/68/test",
|
||
bytes.NewReader([]byte(`{"model_id":"claude-opus-4-6","prompt":""}`)))
|
||
req.Header.Set("Content-Type", "application/json")
|
||
req.Header.Set("Authorization", "Bearer test-token")
|
||
|
||
w := httptest.NewRecorder()
|
||
router.ServeHTTP(w, req)
|
||
|
||
// 模拟 curl 读取响应
|
||
body := w.Body.String()
|
||
|
||
t.Log("curl 会看到:")
|
||
t.Log("```")
|
||
t.Log(body)
|
||
t.Log("```")
|
||
})
|
||
}
|
||
|
||
// 辅助函数:提取 SSE 事件中的错误信息
|
||
func extractErrorFromSSE(sseBody string) string {
|
||
lines := bytes.Split([]byte(sseBody), []byte("\n\n"))
|
||
for _, line := range lines {
|
||
if bytes.HasPrefix(line, []byte("data: ")) {
|
||
data := bytes.TrimPrefix(line, []byte("data: "))
|
||
var event map[string]interface{}
|
||
if err := json.Unmarshal(data, &event); err != nil {
|
||
continue
|
||
}
|
||
if errMsg, ok := event["error"]; ok {
|
||
return errMsg.(string)
|
||
}
|
||
}
|
||
}
|
||
return ""
|
||
}
|