package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "testing" "time" "git.x2erp.com/qdy/go-svc-code/internal/opencode" ) // TestPromptSync 测试同步对话API func TestPromptSync(t *testing.T) { // 获取svc-code服务地址 svcCodeURL := "http://localhost:8020" // 检查服务是否运行 if !checkServiceRunningForPromptTest(t, svcCodeURL) { t.Skipf("svc-code服务未运行在 %s,跳过测试", svcCodeURL) } // 1. 用户登录获取token token, err := loginForPromptTest(t, svcCodeURL) if err != nil { t.Fatalf("登录失败: %v", err) } t.Logf("获取到Token: %s...", token[:minPromptInt(20, len(token))]) // 2. 创建会话 sessionID, err := createSessionForPromptTest(t, svcCodeURL, token, "同步对话测试") if err != nil { t.Logf("创建会话失败: %v (可能是OpenCode连接问题)", err) t.Skip("需要有效的OpenCode连接才能继续测试") } t.Logf("创建会话成功: %s", sessionID) // 3. 发送同步对话请求 response, err := sendSyncPrompt(t, svcCodeURL, token, sessionID, "你好,请做一个简单的测试回复") if err != nil { t.Fatalf("发送同步对话失败: %v", err) } // 4. 验证响应 t.Logf("同步对话响应成功") if response.Info.Content != "" { t.Logf("AI回复: %s", response.Info.Content) } else { t.Log("AI回复内容为空(可能是OpenCode响应格式问题)") } // 5. 验证响应结构 if response.Info.ID == "" { t.Error("响应缺少ID字段") } if response.Info.Role != "assistant" { t.Errorf("预期role为'assistant',得到 %s", response.Info.Role) } if response.Info.SessionID != sessionID { t.Errorf("响应sessionID不匹配,预期 %s,得到 %s", sessionID, response.Info.SessionID) } } // sendSyncPrompt 发送同步对话请求 func sendSyncPrompt(t *testing.T, svcCodeURL, token, sessionID, message string) (*opencode.PromptResponse, error) { url := svcCodeURL + "/api/prompt/sync" requestData := map[string]interface{}{ "sessionID": sessionID, "parts": []opencode.TextPart{ { Type: "text", Text: message, }, }, } jsonData, err := json.Marshal(requestData) if err != nil { return nil, fmt.Errorf("编码请求数据失败: %w", err) } req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("创建请求失败: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("HTTP请求失败: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("请求失败 (状态码 %d): %s", resp.StatusCode, string(bodyBytes)) } var result struct { Success bool `json:"success"` Data opencode.PromptResponse `json:"data"` Message string `json:"message"` } bodyBytes, _ := io.ReadAll(resp.Body) if err := json.Unmarshal(bodyBytes, &result); err != nil { return nil, fmt.Errorf("解析响应失败: %w", err) } if !result.Success { return nil, fmt.Errorf("同步对话失败: %s", result.Message) } return &result.Data, nil } // createSessionForPromptTest 创建会话(提示测试专用) func createSessionForPromptTest(t *testing.T, svcCodeURL, token, title string) (string, error) { url := svcCodeURL + "/api/session/create" sessionData := map[string]string{ "title": title, } jsonData, _ := json.Marshal(sessionData) req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) if err != nil { return "", fmt.Errorf("创建请求失败: %v", err) } req.Header.Set("Authorization", "Bearer "+token) req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("HTTP请求失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) return "", fmt.Errorf("创建会话失败 (状态码 %d): %s", resp.StatusCode, string(bodyBytes)) } var result struct { Success bool `json:"success"` Data struct { ID string `json:"id"` } `json:"data"` Message string `json:"message"` } bodyBytes, _ := io.ReadAll(resp.Body) if err := json.Unmarshal(bodyBytes, &result); err != nil { return "", fmt.Errorf("解析响应失败: %v", err) } if !result.Success { return "", fmt.Errorf("创建会话失败: %s", result.Message) } return result.Data.ID, nil } // loginForPromptTest 登录获取token(提示测试专用) func loginForPromptTest(t *testing.T, svcCodeURL string) (string, error) { loginURL := svcCodeURL + "/api/auth/login" loginData := map[string]string{ "user_id": "test-user-001", "password": "password123", } jsonData, _ := json.Marshal(loginData) resp, err := http.Post(loginURL, "application/json", bytes.NewBuffer(jsonData)) if err != nil { return "", fmt.Errorf("登录请求失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) return "", fmt.Errorf("登录失败 (状态码 %d): %s", resp.StatusCode, string(bodyBytes)) } var result struct { Success bool `json:"success"` Data string `json:"data"` Message string `json:"message"` } bodyBytes, _ := io.ReadAll(resp.Body) if err := json.Unmarshal(bodyBytes, &result); err != nil { return "", fmt.Errorf("解析响应失败: %v", err) } if !result.Success { return "", fmt.Errorf("登录失败: %s", result.Message) } return result.Data, nil } // checkServiceRunningForPromptTest 检查服务是否运行(提示测试专用) func checkServiceRunningForPromptTest(t *testing.T, url string) bool { client := &http.Client{Timeout: 3 * time.Second} resp, err := client.Get(url + "/api/health") if err != nil { // 尝试其他端点 resp, err = client.Get(url) if err != nil { return false } } defer resp.Body.Close() return resp.StatusCode == http.StatusOK || resp.StatusCode == 404 } // minPromptInt 返回两个整数的最小值(提示测试专用) func minPromptInt(a, b int) int { if a < b { return a } return b }