package converter import ( "fmt" "strings" "testing" ) func TestConvertLineAssistant(t *testing.T) { line := `{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello, world!"}]}}` result := ConvertLine(line, "chat-123") if result.Skip { t.Error("expected not Skip") } if result.Done { t.Error("expected not Done") } if result.Error != nil { t.Fatalf("unexpected error: %v", result.Error) } if result.Chunk == nil { t.Fatal("expected Chunk, got nil") } if result.Chunk.ID != "chat-123" { t.Errorf("Chunk.ID = %q, want %q", result.Chunk.ID, "chat-123") } if result.Chunk.Object != "chat.completion.chunk" { t.Errorf("Chunk.Object = %q, want %q", result.Chunk.Object, "chat.completion.chunk") } if len(result.Chunk.Choices) != 1 { t.Fatalf("len(Choices) = %d, want 1", len(result.Chunk.Choices)) } if *result.Chunk.Choices[0].Delta.Content != "Hello, world!" { t.Errorf("Delta.Content = %q, want %q", *result.Chunk.Choices[0].Delta.Content, "Hello, world!") } } func TestConvertLineSystem(t *testing.T) { line := `{"type":"system","message":{"role":"system","content":"init"}}` result := ConvertLine(line, "chat-123") if !result.Skip { t.Error("expected Skip for system line") } if result.Chunk != nil { t.Error("expected nil Chunk for system line") } if result.Error != nil { t.Errorf("unexpected error: %v", result.Error) } } func TestConvertLineUser(t *testing.T) { line := `{"type":"user","message":{"role":"user","content":"hello"}}` result := ConvertLine(line, "chat-123") if !result.Skip { t.Error("expected Skip for user line") } if result.Chunk != nil { t.Error("expected nil Chunk for user line") } if result.Error != nil { t.Errorf("unexpected error: %v", result.Error) } } func TestConvertLineResultSuccess(t *testing.T) { line := `{"type":"result","subtype":"success","result":"done","usage":{"inputTokens":100,"outputTokens":50}}` result := ConvertLine(line, "chat-123") if !result.Done { t.Error("expected Done") } if result.Skip { t.Error("expected not Skip") } if result.Error != nil { t.Fatalf("unexpected error: %v", result.Error) } if result.Usage == nil { t.Fatal("expected Usage, got nil") } if result.Usage.InputTokens != 100 { t.Errorf("Usage.InputTokens = %d, want 100", result.Usage.InputTokens) } if result.Usage.OutputTokens != 50 { t.Errorf("Usage.OutputTokens = %d, want 50", result.Usage.OutputTokens) } } func TestConvertLineResultError(t *testing.T) { line := `{"type":"result","is_error":true,"result":"something went wrong"}` result := ConvertLine(line, "chat-123") if result.Error == nil { t.Fatal("expected error, got nil") } if !strings.Contains(result.Error.Error(), "something went wrong") { t.Errorf("error = %q, want to contain %q", result.Error.Error(), "something went wrong") } if result.Done { t.Error("expected not Done for error") } } func TestConvertLineEmpty(t *testing.T) { tests := []string{"", " ", "\n", " \n "} for _, line := range tests { result := ConvertLine(line, "chat-123") if !result.Skip { t.Errorf("expected Skip for empty/whitespace line %q", line) } if result.Error != nil { t.Errorf("unexpected error for empty line: %v", result.Error) } } } func TestConvertLineInvalidJSON(t *testing.T) { line := `{"type":"assistant", invalid json}` result := ConvertLine(line, "chat-123") if result.Error == nil { t.Fatal("expected error for invalid JSON, got nil") } if !strings.Contains(result.Error.Error(), "unmarshal error") { t.Errorf("error = %q, want to contain %q", result.Error.Error(), "unmarshal error") } } func TestExtractContent(t *testing.T) { t.Run("nil message", func(t *testing.T) { result := ExtractContent(nil) if result != "" { t.Errorf("ExtractContent(nil) = %q, want empty", result) } }) t.Run("single content", func(t *testing.T) { msg := &CursorMessage{ Role: "assistant", Content: []CursorContent{ {Type: "text", Text: "Hello"}, }, } result := ExtractContent(msg) if result != "Hello" { t.Errorf("ExtractContent() = %q, want %q", result, "Hello") } }) t.Run("multiple content", func(t *testing.T) { msg := &CursorMessage{ Role: "assistant", Content: []CursorContent{ {Type: "text", Text: "Hello"}, {Type: "text", Text: ", "}, {Type: "text", Text: "world!"}, }, } result := ExtractContent(msg) if result != "Hello, world!" { t.Errorf("ExtractContent() = %q, want %q", result, "Hello, world!") } }) t.Run("empty content", func(t *testing.T) { msg := &CursorMessage{ Role: "assistant", Content: []CursorContent{}, } result := ExtractContent(msg) if result != "" { t.Errorf("ExtractContent() = %q, want empty", result) } }) } func TestStreamParser_OnlyEmitsNewDeltaFromAccumulatedAssistantMessages(t *testing.T) { parser := NewStreamParser("chat-123") first := parser.Parse(`{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hel"}]}}`) if first.Error != nil { t.Fatalf("unexpected error on first chunk: %v", first.Error) } if first.Chunk == nil || first.Chunk.Choices[0].Delta.Content == nil { t.Fatal("expected first chunk content") } if got := *first.Chunk.Choices[0].Delta.Content; got != "Hel" { t.Fatalf("first delta = %q, want %q", got, "Hel") } second := parser.Parse(`{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}`) if second.Error != nil { t.Fatalf("unexpected error on second chunk: %v", second.Error) } if second.Chunk == nil || second.Chunk.Choices[0].Delta.Content == nil { t.Fatal("expected second chunk content") } if got := *second.Chunk.Choices[0].Delta.Content; got != "lo" { t.Fatalf("second delta = %q, want %q", got, "lo") } } func TestStreamParser_SkipsFinalDuplicateAssistantMessage(t *testing.T) { parser := NewStreamParser("chat-123") first := parser.Parse(`{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}`) if first.Skip || first.Error != nil || first.Chunk == nil { t.Fatalf("expected first assistant chunk, got %+v", first) } duplicate := parser.Parse(`{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}`) if !duplicate.Skip { t.Fatalf("expected duplicate assistant message to be skipped, got %+v", duplicate) } } func TestStreamParser_ResultIncludesUsage(t *testing.T) { parser := NewStreamParser("chat-123") result := parser.Parse(`{"type":"result","subtype":"success","usage":{"inputTokens":10,"outputTokens":4}}`) if !result.Done { t.Fatal("expected result.Done") } if result.Usage == nil { t.Fatal("expected usage") } if result.Usage.InputTokens != 10 || result.Usage.OutputTokens != 4 { t.Fatalf("unexpected usage: %+v", result.Usage) } } func TestStreamParser_CanReconstructFinalContentFromIncrementalAssistantMessages(t *testing.T) { parser := NewStreamParser("chat-123") lines := []string{ `{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"你"}]}}`, `{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"你好"}]}}`, `{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"你好,世界"}]}}`, } var content strings.Builder for i, line := range lines { result := parser.Parse(line) if result.Error != nil { t.Fatalf("line %d unexpected error: %v", i, result.Error) } if result.Skip { continue } if result.Chunk == nil || result.Chunk.Choices[0].Delta.Content == nil { t.Fatalf("line %d expected chunk content, got %+v", i, result) } content.WriteString(*result.Chunk.Choices[0].Delta.Content) } if got := content.String(); got != "你好,世界" { t.Fatalf("reconstructed content = %q, want %q", got, "你好,世界") } } func TestStreamParser_RawTextFallbackSkipsExactDuplicates(t *testing.T) { parser := NewStreamParser("chat-123") first := parser.ParseRawText("plain chunk") if first.Skip || first.Chunk == nil || first.Chunk.Choices[0].Delta.Content == nil { t.Fatalf("expected raw text chunk, got %+v", first) } duplicate := parser.ParseRawText("plain chunk") if !duplicate.Skip { t.Fatalf("expected duplicate raw text to be skipped, got %+v", duplicate) } } func TestStreamParser_IncrementalFragmentsAccumulateAndSkipFinalDuplicate(t *testing.T) { parser := NewStreamParser("chat-123") fragments := []string{"你", "好,", "世", "界!"} var got strings.Builder for i, fr := range fragments { line := fmt.Sprintf(`{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":%q}]}}`, fr) res := parser.Parse(line) if res.Skip || res.Error != nil || res.Chunk == nil { t.Fatalf("fragment %d: expected delta chunk, got %+v", i, res) } got.WriteString(*res.Chunk.Choices[0].Delta.Content) } if got.String() != "你好,世界!" { t.Fatalf("reconstructed = %q, want 你好,世界!", got.String()) } final := parser.Parse(`{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"你好,世界!"}]}}`) if !final.Skip { t.Fatalf("expected final duplicate cumulative message to be skipped, got %+v", final) } } func TestNewStreamParser_DoesNotExistYet(t *testing.T) { _ = fmt.Sprintf }