Няма описание
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

logs_stream_test.go 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. package main
  2. import (
  3. "bufio"
  4. "bytes"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "strings"
  10. "testing"
  11. "time"
  12. )
  13. // TestLogsStream 测试日志流API
  14. func TestLogsStream(t *testing.T) {
  15. // 获取svc-code服务地址
  16. svcCodeURL := "http://localhost:8020"
  17. // 检查服务是否运行
  18. if !checkServiceRunningForLogsTest(t, svcCodeURL) {
  19. t.Skipf("svc-code服务未运行在 %s,跳过测试", svcCodeURL)
  20. }
  21. // 1. 用户登录获取token
  22. token, err := loginForLogsTest(t, svcCodeURL)
  23. if err != nil {
  24. t.Fatalf("登录失败: %v", err)
  25. }
  26. t.Logf("获取到Token: %s...", token[:minLogsInt(20, len(token))])
  27. // 2. 连接日志流
  28. eventCount, err := connectToLogsStream(t, svcCodeURL, token, "")
  29. if err != nil {
  30. t.Fatalf("连接日志流失败: %v", err)
  31. }
  32. // 3. 验证日志流
  33. if eventCount == 0 {
  34. t.Log("警告: 未收到任何日志事件(可能是OpenCode未产生日志)")
  35. } else {
  36. t.Logf("成功收到 %d 个日志事件", eventCount)
  37. }
  38. // 4. 测试带sessionId过滤的日志流
  39. t.Run("WithSessionFilter", func(t *testing.T) {
  40. testLogsStreamWithSessionFilter(t, svcCodeURL, token)
  41. })
  42. }
  43. // connectToLogsStream 连接日志流并统计事件数量
  44. func connectToLogsStream(t *testing.T, svcCodeURL, token, sessionId string) (int, error) {
  45. url := svcCodeURL + "/api/logs/stream"
  46. if sessionId != "" {
  47. url += "?sessionId=" + sessionId
  48. }
  49. req, err := http.NewRequest("GET", url, nil)
  50. if err != nil {
  51. return 0, fmt.Errorf("创建请求失败: %w", err)
  52. }
  53. req.Header.Set("Authorization", "Bearer "+token)
  54. req.Header.Set("Accept", "text/event-stream")
  55. client := &http.Client{Timeout: 15 * time.Second}
  56. resp, err := client.Do(req)
  57. if err != nil {
  58. return 0, fmt.Errorf("HTTP请求失败: %w", err)
  59. }
  60. defer resp.Body.Close()
  61. t.Logf("日志流响应状态: %d %s", resp.StatusCode, resp.Status)
  62. t.Logf("Content-Type: %s", resp.Header.Get("content-type"))
  63. if resp.StatusCode != http.StatusOK {
  64. bodyBytes, _ := io.ReadAll(resp.Body)
  65. return 0, fmt.Errorf("请求失败 (状态码 %d): %s", resp.StatusCode, string(bodyBytes))
  66. }
  67. // 解析SSE响应
  68. reader := bufio.NewReader(resp.Body)
  69. eventCount := 0
  70. timeout := time.After(10 * time.Second)
  71. for {
  72. select {
  73. case <-timeout:
  74. t.Log("日志流读取超时")
  75. return eventCount, nil
  76. default:
  77. line, err := reader.ReadString('\n')
  78. if err != nil {
  79. if err == io.EOF {
  80. t.Log("日志流结束")
  81. return eventCount, nil
  82. }
  83. return eventCount, fmt.Errorf("读取响应失败: %w", err)
  84. }
  85. line = strings.TrimSpace(line)
  86. if line == "" {
  87. continue
  88. }
  89. if strings.HasPrefix(line, "data: ") {
  90. data := strings.TrimPrefix(line, "data: ")
  91. eventCount++
  92. t.Logf("收到日志[%d]: %s", eventCount, data)
  93. } else if strings.HasPrefix(line, ":") {
  94. // 心跳消息,忽略
  95. continue
  96. }
  97. }
  98. }
  99. }
  100. // testLogsStreamWithSessionFilter 测试带sessionId过滤的日志流
  101. func testLogsStreamWithSessionFilter(t *testing.T, svcCodeURL, token string) {
  102. // 先创建一个会话,获取sessionId
  103. sessionID, err := createSessionForLogsTest(t, svcCodeURL, token, "日志流测试会话")
  104. if err != nil {
  105. t.Logf("创建会话失败: %v (跳过过滤测试)", err)
  106. return
  107. }
  108. t.Logf("使用sessionId过滤日志: %s", sessionID)
  109. eventCount, err := connectToLogsStream(t, svcCodeURL, token, sessionID)
  110. if err != nil {
  111. t.Errorf("带过滤的日志流连接失败: %v", err)
  112. return
  113. }
  114. t.Logf("过滤日志流收到 %d 个事件", eventCount)
  115. }
  116. // createSessionForLogsTest 创建会话(日志测试专用)
  117. func createSessionForLogsTest(t *testing.T, svcCodeURL, token, title string) (string, error) {
  118. url := svcCodeURL + "/api/session/create"
  119. sessionData := map[string]string{
  120. "title": title,
  121. }
  122. jsonData, _ := json.Marshal(sessionData)
  123. req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
  124. if err != nil {
  125. return "", fmt.Errorf("创建请求失败: %v", err)
  126. }
  127. req.Header.Set("Authorization", "Bearer "+token)
  128. req.Header.Set("Content-Type", "application/json")
  129. client := &http.Client{Timeout: 10 * time.Second}
  130. resp, err := client.Do(req)
  131. if err != nil {
  132. return "", fmt.Errorf("HTTP请求失败: %v", err)
  133. }
  134. defer resp.Body.Close()
  135. if resp.StatusCode != http.StatusOK {
  136. bodyBytes, _ := io.ReadAll(resp.Body)
  137. return "", fmt.Errorf("创建会话失败 (状态码 %d): %s", resp.StatusCode, string(bodyBytes))
  138. }
  139. var result struct {
  140. Success bool `json:"success"`
  141. Data struct {
  142. ID string `json:"id"`
  143. } `json:"data"`
  144. Message string `json:"message"`
  145. }
  146. bodyBytes, _ := io.ReadAll(resp.Body)
  147. if err := json.Unmarshal(bodyBytes, &result); err != nil {
  148. return "", fmt.Errorf("解析响应失败: %v", err)
  149. }
  150. if !result.Success {
  151. return "", fmt.Errorf("创建会话失败: %s", result.Message)
  152. }
  153. return result.Data.ID, nil
  154. }
  155. // loginForLogsTest 登录获取token(日志测试专用)
  156. func loginForLogsTest(t *testing.T, svcCodeURL string) (string, error) {
  157. loginURL := svcCodeURL + "/api/auth/login"
  158. loginData := map[string]string{
  159. "user_id": "test-user-001",
  160. "password": "password123",
  161. }
  162. jsonData, _ := json.Marshal(loginData)
  163. resp, err := http.Post(loginURL, "application/json", bytes.NewBuffer(jsonData))
  164. if err != nil {
  165. return "", fmt.Errorf("登录请求失败: %v", err)
  166. }
  167. defer resp.Body.Close()
  168. if resp.StatusCode != http.StatusOK {
  169. bodyBytes, _ := io.ReadAll(resp.Body)
  170. return "", fmt.Errorf("登录失败 (状态码 %d): %s", resp.StatusCode, string(bodyBytes))
  171. }
  172. var result struct {
  173. Success bool `json:"success"`
  174. Data string `json:"data"`
  175. Message string `json:"message"`
  176. }
  177. bodyBytes, _ := io.ReadAll(resp.Body)
  178. if err := json.Unmarshal(bodyBytes, &result); err != nil {
  179. return "", fmt.Errorf("解析响应失败: %v", err)
  180. }
  181. if !result.Success {
  182. return "", fmt.Errorf("登录失败: %s", result.Message)
  183. }
  184. return result.Data, nil
  185. }
  186. // checkServiceRunningForLogsTest 检查服务是否运行(日志测试专用)
  187. func checkServiceRunningForLogsTest(t *testing.T, url string) bool {
  188. client := &http.Client{Timeout: 3 * time.Second}
  189. resp, err := client.Get(url + "/api/health")
  190. if err != nil {
  191. // 尝试其他端点
  192. resp, err = client.Get(url)
  193. if err != nil {
  194. return false
  195. }
  196. }
  197. defer resp.Body.Close()
  198. return resp.StatusCode == http.StatusOK || resp.StatusCode == 404
  199. }
  200. // minLogsInt 返回两个整数的最小值(日志测试专用)
  201. func minLogsInt(a, b int) int {
  202. if a < b {
  203. return a
  204. }
  205. return b
  206. }