package parser import ( "encoding/json" "testing" ) func makeAssistantLine(text string) string { obj := map[string]interface{}{ "type": "assistant", "message": map[string]interface{}{ "content": []map[string]interface{}{ {"type": "text", "text": text}, }, }, } b, _ := json.Marshal(obj) return string(b) } func makeResultLine() string { b, _ := json.Marshal(map[string]string{"type": "result", "subtype": "success"}) return string(b) } func TestStreamParserIncrementalDeltas(t *testing.T) { var texts []string doneCount := 0 parse := CreateStreamParser( func(text string) { texts = append(texts, text) }, func() { doneCount++ }, ) parse(makeAssistantLine("Hello")) if len(texts) != 1 || texts[0] != "Hello" { t.Fatalf("expected [Hello], got %v", texts) } parse(makeAssistantLine("Hello world")) if len(texts) != 2 || texts[1] != " world" { t.Fatalf("expected second call with ' world', got %v", texts) } } func TestStreamParserDeduplicatesFinalMessage(t *testing.T) { var texts []string parse := CreateStreamParser( func(text string) { texts = append(texts, text) }, func() {}, ) parse(makeAssistantLine("Hi")) parse(makeAssistantLine("Hi there")) if len(texts) != 2 { t.Fatalf("expected 2 calls, got %d: %v", len(texts), texts) } if texts[0] != "Hi" || texts[1] != " there" { t.Fatalf("unexpected texts: %v", texts) } // Final duplicate: full accumulated text again parse(makeAssistantLine("Hi there")) if len(texts) != 2 { t.Fatalf("expected no new call after duplicate, got %d: %v", len(texts), texts) } } func TestStreamParserCallsOnDone(t *testing.T) { var texts []string doneCount := 0 parse := CreateStreamParser( func(text string) { texts = append(texts, text) }, func() { doneCount++ }, ) parse(makeResultLine()) if doneCount != 1 { t.Fatalf("expected onDone called once, got %d", doneCount) } if len(texts) != 0 { t.Fatalf("expected no text, got %v", texts) } } func TestStreamParserIgnoresLinesAfterDone(t *testing.T) { var texts []string doneCount := 0 parse := CreateStreamParser( func(text string) { texts = append(texts, text) }, func() { doneCount++ }, ) parse(makeResultLine()) parse(makeAssistantLine("late")) if len(texts) != 0 { t.Fatalf("expected no text after done, got %v", texts) } if doneCount != 1 { t.Fatalf("expected onDone called once, got %d", doneCount) } } func TestStreamParserIgnoresNonAssistantLines(t *testing.T) { var texts []string parse := CreateStreamParser( func(text string) { texts = append(texts, text) }, func() {}, ) b1, _ := json.Marshal(map[string]interface{}{"type": "user", "message": map[string]interface{}{}}) parse(string(b1)) b2, _ := json.Marshal(map[string]interface{}{ "type": "assistant", "message": map[string]interface{}{"content": []interface{}{}}, }) parse(string(b2)) parse(`{"type":"assistant","message":{"content":[{"type":"code","text":"x"}]}}`) if len(texts) != 0 { t.Fatalf("expected no texts, got %v", texts) } } func TestStreamParserIgnoresParseErrors(t *testing.T) { var texts []string doneCount := 0 parse := CreateStreamParser( func(text string) { texts = append(texts, text) }, func() { doneCount++ }, ) parse("not json") parse("{") parse("") if len(texts) != 0 || doneCount != 0 { t.Fatalf("expected nothing, got texts=%v done=%d", texts, doneCount) } } func TestStreamParserJoinsMultipleTextParts(t *testing.T) { var texts []string parse := CreateStreamParser( func(text string) { texts = append(texts, text) }, func() {}, ) obj := map[string]interface{}{ "type": "assistant", "message": map[string]interface{}{ "content": []map[string]interface{}{ {"type": "text", "text": "Hello"}, {"type": "text", "text": " "}, {"type": "text", "text": "world"}, }, }, } b, _ := json.Marshal(obj) parse(string(b)) if len(texts) != 1 || texts[0] != "Hello world" { t.Fatalf("expected ['Hello world'], got %v", texts) } }