sse_test.go 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. package agentserver
  2. import (
  3. "strings"
  4. "testing"
  5. )
  6. const sid = "ses_01a"
  7. func TestSSEScannerChunked(t *testing.T) {
  8. stream := "retry: 2000\n\n" +
  9. ": ping\n\n" +
  10. `id: 1` + "\nevent: run.started\ndata: " + `{"seq":1,"type":"run.started","sessionId":"ses_01a","runId":"run_01r","payload":{"provider":"claude","model":"sonnet"}}` + "\n\n" +
  11. `id: 2` + "\r\nevent: interaction.required\r\ndata: " + `{"seq":2,"type":"interaction.required","sessionId":"ses_01a","runId":"run_01r","payload":{"interactionId":"itx_01i","kind":"approval"}}` + "\r\n\r\n" +
  12. `id: 3` + "\nevent: file.changed\ndata: " + `{"seq":3,"type":"file.changed","sessionId":"ses_01a","runId":"run_01r","payload":{"fileId":"file_01f","path":"outputs/a.docx"}}` + "\n\n" +
  13. `id: 4` + "\nevent: message.delta\ndata: " + `{"seq":4,"type":"message.delta","sessionId":"ses_01a","runId":"run_01r","payload":{"text":"hi"}}` + "\n\n"
  14. var got []string
  15. s := NewSSEScanner(func(data []byte) {
  16. for _, r := range ResourcesFromSSEData(data) {
  17. got = append(got, r.Id+"@"+r.SessionId)
  18. }
  19. })
  20. // 按 7 字节切块喂入,覆盖事件跨块的情况
  21. for i := 0; i < len(stream); i += 7 {
  22. end := i + 7
  23. if end > len(stream) {
  24. end = len(stream)
  25. }
  26. s.Write([]byte(stream[i:end]))
  27. }
  28. want := "run_01r@ses_01a,itx_01i@ses_01a,file_01f@ses_01a"
  29. if strings.Join(got, ",") != want {
  30. t.Fatalf("got %v, want %s", got, want)
  31. }
  32. }
  33. func TestResourcesFromResponse(t *testing.T) {
  34. cases := []struct {
  35. action Action
  36. body string
  37. want string
  38. }{
  39. {ActionSendMessage, `{"id":"run_01r","sessionId":"ses_01a","status":"queued"}`, "run_01r"},
  40. {ActionUploadFiles, `[{"id":"file_1","sessionId":"ses_01a"},{"id":"file_2","sessionId":"ses_01a"}]`, "file_1,file_2"},
  41. {ActionListFiles, `[{"id":"file_3","sessionId":"ses_01a"}]`, "file_3"},
  42. {ActionEvents, `{"items":[{"seq":1,"type":"run.started","sessionId":"ses_01a","runId":"run_9","payload":{}},` +
  43. `{"seq":2,"type":"file.changed","sessionId":"ses_OTHER","runId":"run_9","payload":{"fileId":"file_x"}}],"lastSeq":2}`, "run_9"},
  44. {ActionSessionDetail, `{"id":"ses_01a","activeRunId":"run_7","pendingInteractions":[{"interactionId":"itx_7"}]}`, "run_7,itx_7"},
  45. {ActionSessionDetail, `{"id":"ses_01a","activeRunId":null,"pendingInteractions":[]}`, ""},
  46. {ActionInteractions, `[{"interactionId":"itx_8","kind":"question"}]`, "itx_8"},
  47. {ActionSendMessage, `not json`, ""},
  48. }
  49. for _, c := range cases {
  50. var ids []string
  51. for _, r := range ResourcesFromResponse(c.action, sid, []byte(c.body)) {
  52. if r.SessionId != sid {
  53. t.Errorf("action %v: 资源 %s 的会话为 %s", c.action, r.Id, r.SessionId)
  54. }
  55. ids = append(ids, r.Id)
  56. }
  57. if strings.Join(ids, ",") != c.want {
  58. t.Errorf("action %v: got %v, want %s", c.action, ids, c.want)
  59. }
  60. }
  61. }
  62. func TestFilterSessionPatch(t *testing.T) {
  63. b, title, err := filterSessionPatch([]byte(`{"title":"新标题","model":"opus","permissionMode":"auto","profileId":"prf_x","systemPromptAppend":"x"}`))
  64. if err != nil {
  65. t.Fatal(err)
  66. }
  67. if string(b) != `{"model":"opus","title":"新标题"}` {
  68. t.Fatalf("过滤结果 %s", b)
  69. }
  70. if title == nil || *title != "新标题" {
  71. t.Fatalf("title %v", title)
  72. }
  73. if _, _, err := filterSessionPatch([]byte(`[1]`)); err == nil {
  74. t.Fatal("非对象应报错")
  75. }
  76. }