| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- package main
-
- import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "testing"
- "time"
-
- "git.x2erp.com/qdy/go-svc-code/internal/opencode"
- )
-
- // TestDirectOpenCodeMessages 直接调用opencode API (8787端口) 获取会话消息
- func TestDirectOpenCodeMessages(t *testing.T) {
- // 外部已启动的OpenCode服务端口
- externalOpenCodePort := 8787
- opencodeURL := fmt.Sprintf("http://127.0.0.1:%d", externalOpenCodePort)
-
- // 检查OpenCode服务是否运行
- if !isServiceRunningForMessages(t, opencodeURL) {
- t.Skipf("OpenCode服务未运行在 %s,跳过测试", opencodeURL)
- }
-
- t.Logf("🚀 开始测试直接调用OpenCode API获取消息")
- t.Logf("OpenCode URL: %s", opencodeURL)
-
- // 使用现有的测试会话ID
- sessionID := "ses_3aa9a94dfffeLIdWcLHCG97m7z"
- t.Logf("测试会话ID: %s", sessionID)
-
- // 1. 使用DirectClient调用(测试SDK接口)
- t.Run("SDKClient", func(t *testing.T) {
- testDirectClientMessages(t, externalOpenCodePort, sessionID)
- })
-
- // 2. 直接HTTP调用(测试原始API)
- t.Run("RawHTTP", func(t *testing.T) {
- testRawHTTPMessages(t, opencodeURL, sessionID)
- })
- }
-
- // testDirectClientMessages 使用DirectClient测试获取消息
- func testDirectClientMessages(t *testing.T, port int, sessionID string) {
- // 创建客户端
- client, err := opencode.NewDirectClient(port)
- if err != nil {
- t.Fatalf("❌ 创建OpenCode客户端失败: %v", err)
- }
- t.Logf("✅ 创建OpenCode客户端成功,基础URL: %s", client.GetBaseURL())
-
- ctx := context.Background()
-
- // 测试不带limit参数
- t.Log("📝 测试获取消息(不带limit)")
- messages, err := client.GetSessionMessages(ctx, sessionID, 0)
- if err != nil {
- t.Errorf("❌ 获取会话消息失败: %v", err)
- return
- }
- t.Logf("✅ 获取到 %d 条消息", len(messages))
-
- // 打印消息摘要
- for i, msg := range messages {
- t.Logf(" 消息[%d]: ID=%s, 角色=%s", i+1, extractMessageID(msg), extractMessageRole(msg))
- if i < 3 && len(messages) > 3 { // 只打印前3条
- if content := extractMessageContent(msg); content != "" {
- t.Logf(" 内容预览: %.100s...", content)
- }
- }
- }
-
- // 测试带limit参数
- t.Log("📝 测试获取消息(limit=5)")
- messagesLimited, err := client.GetSessionMessages(ctx, sessionID, 5)
- if err != nil {
- t.Errorf("❌ 获取限制数量的会话消息失败: %v", err)
- return
- }
- t.Logf("✅ 获取到 %d 条消息(限制5条)", len(messagesLimited))
-
- if len(messages) > 0 && len(messagesLimited) > 0 {
- if len(messagesLimited) > 5 {
- t.Errorf("❌ 消息数量超过限制: %d > 5", len(messagesLimited))
- }
- }
- }
-
- // testRawHTTPMessages 直接HTTP调用测试获取消息
- func testRawHTTPMessages(t *testing.T, baseURL, sessionID string) {
- // 构造URL
- url := fmt.Sprintf("%s/session/%s/message", baseURL, sessionID)
- t.Logf("📡 调用原始API: %s", url)
-
- // 发送HTTP请求
- client := &http.Client{Timeout: 10 * time.Second}
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("❌ 创建请求失败: %v", err)
- }
- req.Header.Set("Accept", "application/json")
-
- resp, err := client.Do(req)
- if err != nil {
- t.Fatalf("❌ HTTP请求失败: %v", err)
- }
- defer resp.Body.Close()
-
- t.Logf("📊 响应状态码: %d", resp.StatusCode)
-
- if resp.StatusCode != http.StatusOK {
- body, _ := io.ReadAll(resp.Body)
- t.Fatalf("❌ 获取会话消息失败,状态码: %d, 响应体: %s", resp.StatusCode, string(body))
- }
-
- // 解析响应
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- t.Fatalf("❌ 读取响应体失败: %v", err)
- }
-
- // 尝试解析为SessionMessage数组
- var messages []opencode.SessionMessage
- if err := json.Unmarshal(body, &messages); err != nil {
- t.Errorf("❌ 解析消息响应失败: %v", err)
- t.Logf("原始响应: %s", string(body[:minForMessagesTest(500, len(body))]))
- return
- }
-
- t.Logf("✅ 解析成功,获取到 %d 条消息", len(messages))
-
- // 验证消息结构
- for i, msg := range messages {
- if extractMessageID(msg) == "" {
- t.Errorf("❌ 消息[%d]缺少ID", i)
- }
- if extractMessageRole(msg) == "" {
- t.Errorf("❌ 消息[%d]缺少角色", i)
- }
- }
-
- // 测试带limit参数
- urlWithLimit := fmt.Sprintf("%s?limit=3", url)
- t.Logf("📡 调用带limit的API: %s", urlWithLimit)
-
- reqLimit, err := http.NewRequest("GET", urlWithLimit, nil)
- if err != nil {
- t.Errorf("❌ 创建带limit的请求失败: %v", err)
- return
- }
- reqLimit.Header.Set("Accept", "application/json")
-
- respLimit, err := client.Do(reqLimit)
- if err != nil {
- t.Errorf("❌ HTTP请求(带limit)失败: %v", err)
- return
- }
- defer respLimit.Body.Close()
-
- if respLimit.StatusCode == http.StatusOK {
- bodyLimit, _ := io.ReadAll(respLimit.Body)
- var messagesLimit []opencode.SessionMessage
- if err := json.Unmarshal(bodyLimit, &messagesLimit); err == nil {
- t.Logf("✅ 带limit获取到 %d 条消息", len(messagesLimit))
- if len(messagesLimit) > 3 {
- t.Errorf("❌ 带limit的消息数量超过限制: %d > 3", len(messagesLimit))
- }
- }
- }
- }
-
- // TestSvcCodeMessagesAPI 通过svc-code API (8020端口) 获取会话消息
- func TestSvcCodeMessagesAPI(t *testing.T) {
- // svc-code服务地址
- svcCodeURL := "http://localhost:8020"
-
- // 检查svc-code服务是否运行
- if !isServiceRunningForMessages(t, svcCodeURL) {
- t.Skipf("svc-code服务未运行在 %s,跳过测试", svcCodeURL)
- }
-
- t.Logf("🚀 开始测试通过svc-code API获取消息")
- t.Logf("svc-code URL: %s", svcCodeURL)
-
- // 使用现有的测试会话ID
- sessionID := "ses_3aa9a94dfffeLIdWcLHCG97m7z"
- t.Logf("测试会话ID: %s", sessionID)
-
- // 1. 用户登录获取token
- token, err := loginAndGetTokenForMessagesTest(t, svcCodeURL)
- if err != nil {
- t.Fatalf("❌ 登录失败: %v", err)
- }
- t.Logf("✅ 获取到Token: %s...", token[:minForMessagesTest(20, len(token))])
-
- // 2. 测试不带limit参数
- t.Run("WithoutLimit", func(t *testing.T) {
- testSvcCodeMessages(t, svcCodeURL, token, sessionID, 0)
- })
-
- // 3. 测试带limit参数
- t.Run("WithLimit", func(t *testing.T) {
- testSvcCodeMessages(t, svcCodeURL, token, sessionID, 10)
- })
-
- // 4. 测试无效token
- t.Run("InvalidToken", func(t *testing.T) {
- testInvalidTokenForMessages(t, svcCodeURL, sessionID)
- })
-
- // 5. 测试无效sessionID
- t.Run("InvalidSessionID", func(t *testing.T) {
- testInvalidSessionIDForMessages(t, svcCodeURL, token)
- })
- }
-
- // testSvcCodeMessages 测试svc-code获取消息API
- func testSvcCodeMessages(t *testing.T, baseURL, token, sessionID string, limit int) {
- // 构造URL
- url := fmt.Sprintf("%s/api/session/messages", baseURL)
-
- // 创建请求
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("❌ 创建请求失败: %v", err)
- }
-
- // 添加查询参数
- q := req.URL.Query()
- q.Add("sessionID", sessionID)
- if limit > 0 {
- q.Add("limit", fmt.Sprintf("%d", limit))
- }
- req.URL.RawQuery = q.Encode()
-
- // 添加认证头
- req.Header.Set("Authorization", "Bearer "+token)
- req.Header.Set("Accept", "application/json")
-
- t.Logf("📡 调用svc-code API: %s", req.URL.String())
-
- // 发送请求
- client := &http.Client{Timeout: 10 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- t.Fatalf("❌ HTTP请求失败: %v", err)
- }
- defer resp.Body.Close()
-
- t.Logf("📊 响应状态码: %d", resp.StatusCode)
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- t.Fatalf("❌ 读取响应体失败: %v", err)
- }
-
- // 解析响应
- var result struct {
- Success bool `json:"success"`
- Message string `json:"message"`
- Data struct {
- Messages []opencode.SessionMessage `json:"messages"`
- Count int `json:"count"`
- } `json:"data"`
- }
-
- if err := json.Unmarshal(body, &result); err != nil {
- t.Errorf("❌ 解析响应失败: %v", err)
- t.Logf("原始响应: %s", string(body[:minForMessagesTest(500, len(body))]))
- return
- }
-
- if !result.Success {
- t.Errorf("❌ API调用失败: %s", result.Message)
- return
- }
-
- t.Logf("✅ 获取成功!消息数量: %d, 总计: %d",
- len(result.Data.Messages), result.Data.Count)
-
- // 验证数据一致性
- if result.Data.Count != len(result.Data.Messages) {
- t.Errorf("❌ 数据不一致: Count=%d, Messages长度=%d",
- result.Data.Count, len(result.Data.Messages))
- }
-
- // 打印消息摘要
- for i, msg := range result.Data.Messages {
- t.Logf(" 消息[%d]: ID=%s, 角色=%s", i+1, extractMessageID(msg), extractMessageRole(msg))
- if i < 2 && len(result.Data.Messages) > 2 {
- if content := extractMessageContent(msg); content != "" {
- t.Logf(" 内容预览: %.100s...", content)
- }
- }
- }
-
- // 验证limit参数效果
- if limit > 0 && len(result.Data.Messages) > limit {
- t.Errorf("❌ 消息数量超过限制: %d > %d", len(result.Data.Messages), limit)
- }
- }
-
- // testInvalidTokenForMessages 测试无效token
- func testInvalidTokenForMessages(t *testing.T, baseURL, sessionID string) {
- url := fmt.Sprintf("%s/api/session/messages?sessionID=%s", baseURL, sessionID)
-
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("创建请求失败: %v", err)
- }
-
- // 使用无效token
- req.Header.Set("Authorization", "Bearer invalid_token_12345")
-
- client := &http.Client{Timeout: 5 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- t.Fatalf("HTTP请求失败: %v", err)
- }
- defer resp.Body.Close()
-
- // 应该返回401或403
- if resp.StatusCode != http.StatusUnauthorized && resp.StatusCode != http.StatusForbidden {
- t.Errorf("预期认证错误状态码(401/403),实际: %d", resp.StatusCode)
- body, _ := io.ReadAll(resp.Body)
- t.Logf("响应体: %s", string(body))
- } else {
- t.Logf("✅ 无效token测试通过,返回状态码: %d", resp.StatusCode)
- }
- }
-
- // testInvalidSessionIDForMessages 测试无效sessionID
- func testInvalidSessionIDForMessages(t *testing.T, baseURL, token string) {
- invalidSessionID := "ses_invalid_12345"
- url := fmt.Sprintf("%s/api/session/messages?sessionID=%s", baseURL, invalidSessionID)
-
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("创建请求失败: %v", err)
- }
-
- req.Header.Set("Authorization", "Bearer "+token)
-
- client := &http.Client{Timeout: 5 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- t.Fatalf("HTTP请求失败: %v", err)
- }
- defer resp.Body.Close()
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- t.Fatalf("读取响应体失败: %v", err)
- }
-
- var result struct {
- Success bool `json:"success"`
- Message string `json:"message"`
- }
-
- if err := json.Unmarshal(body, &result); err != nil {
- t.Errorf("解析响应失败: %v", err)
- return
- }
-
- if result.Success {
- t.Errorf("无效sessionID应该返回失败,实际成功")
- } else {
- t.Logf("✅ 无效sessionID测试通过,错误信息: %s", result.Message)
- }
- }
-
- // loginAndGetTokenForMessagesTest 登录获取token
- func loginAndGetTokenForMessagesTest(t *testing.T, baseURL string) (string, error) {
- loginURL := baseURL + "/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
- }
-
- // isServiceRunningForMessages 检查服务是否运行
- func isServiceRunningForMessages(t *testing.T, url string) bool {
- client := &http.Client{Timeout: 3 * time.Second}
-
- // 尝试多个端点
- endpoints := []string{
- "/global/health", // opencode健康检查
- "/api/auth/health", // svc-code健康检查
- "/api/health", // 其他健康检查
- "", // 根路径
- }
-
- for _, endpoint := range endpoints {
- fullURL := url + endpoint
- resp, err := client.Get(fullURL)
- if err == nil {
- resp.Body.Close()
- if resp.StatusCode == http.StatusOK {
- t.Logf("✅ 服务运行正常: %s (状态码: %d)", fullURL, resp.StatusCode)
- return true
- }
- }
- }
-
- return false
- }
-
- // extractMessageID 从SessionMessage提取消息ID
- func extractMessageID(msg opencode.SessionMessage) string {
- if msg.Info != nil {
- if id, ok := msg.Info["id"].(string); ok {
- return id
- }
- }
- return ""
- }
-
- // extractMessageRole 从SessionMessage提取消息角色
- func extractMessageRole(msg opencode.SessionMessage) string {
- if msg.Info != nil {
- if role, ok := msg.Info["role"].(string); ok {
- return role
- }
- }
- return ""
- }
-
- // extractMessageContent 从SessionMessage提取消息内容
- func extractMessageContent(msg opencode.SessionMessage) string {
- if len(msg.Parts) > 0 {
- for _, part := range msg.Parts {
- if text, ok := part["text"].(string); ok && text != "" {
- return text
- }
- }
- }
- return ""
- }
-
- // minForMessagesTest 返回两个整数的最小值
- func minForMessagesTest(a, b int) int {
- if a < b {
- return a
- }
- return b
- }
|