package shared
import (
"testing"
"ds2api/internal/sse"
)
func TestStreamAccumulatorAppliesThinkingAndTextDedupe(t *testing.T) {
acc := StreamAccumulator{ThinkingEnabled: true, StripReferenceMarkers: true}
thinkingPrefix := "this is a long thinking snapshot prefix used by DeepSeek continue replay"
textPrefix := "this is a long visible answer snapshot prefix used by DeepSeek continue replay"
first := acc.Apply(sse.LineResult{
Parsed: true,
Parts: []sse.ContentPart{
{Type: "thinking", Text: thinkingPrefix},
{Type: "text", Text: textPrefix},
},
})
second := acc.Apply(sse.LineResult{
Parsed: true,
Parts: []sse.ContentPart{
{Type: "thinking", Text: thinkingPrefix + " next"},
{Type: "text", Text: textPrefix + " world"},
},
})
if !first.ContentSeen || !second.ContentSeen {
t.Fatalf("expected both chunks to mark content seen")
}
if got := acc.RawThinking.String(); got != thinkingPrefix+" next" {
t.Fatalf("raw thinking = %q", got)
}
if got := acc.Thinking.String(); got != thinkingPrefix+" next" {
t.Fatalf("thinking = %q", got)
}
if got := acc.RawText.String(); got != textPrefix+" world" {
t.Fatalf("raw text = %q", got)
}
if got := acc.Text.String(); got != textPrefix+" world" {
t.Fatalf("text = %q", got)
}
if got := second.Parts[0].VisibleText; got != " next" {
t.Fatalf("thinking delta = %q", got)
}
if got := second.Parts[1].VisibleText; got != " world" {
t.Fatalf("text delta = %q", got)
}
}
func TestStreamAccumulatorKeepsHiddenThinkingForToolDetection(t *testing.T) {
acc := StreamAccumulator{ThinkingEnabled: false, StripReferenceMarkers: true}
result := acc.Apply(sse.LineResult{
Parsed: true,
Parts: []sse.ContentPart{
{Type: "thinking", Text: ""},
},
ToolDetectionThinkingParts: []sse.ContentPart{
{Type: "thinking", Text: "detect"},
{Type: "thinking", Text: " tools"},
},
})
if !result.ContentSeen {
t.Fatalf("expected hidden thinking to count as upstream content")
}
if got := acc.RawThinking.String(); got != "" {
t.Fatalf("raw thinking = %q", got)
}
if got := acc.Thinking.String(); got != "" {
t.Fatalf("visible thinking = %q", got)
}
if got := acc.ToolDetectionThinking.String(); got != "detect tools" {
t.Fatalf("tool detection thinking = %q", got)
}
}
func TestStreamAccumulatorSuppressesCitationTextWhenSearchEnabled(t *testing.T) {
acc := StreamAccumulator{SearchEnabled: true, StripReferenceMarkers: true}
result := acc.Apply(sse.LineResult{
Parsed: true,
Parts: []sse.ContentPart{{Type: "text", Text: "[citation:1]"}},
})
if !result.ContentSeen {
t.Fatalf("expected citation chunk to mark upstream content")
}
if len(result.Parts) != 1 || !result.Parts[0].CitationOnly {
t.Fatalf("expected citation-only delta, got %#v", result.Parts)
}
if got := acc.RawText.String(); got != "[citation:1]" {
t.Fatalf("raw text = %q", got)
}
if got := acc.Text.String(); got != "" {
t.Fatalf("visible text = %q", got)
}
}
func TestStreamAccumulatorStripsInlineCitationAndReferenceMarkers(t *testing.T) {
acc := StreamAccumulator{SearchEnabled: true, StripReferenceMarkers: true}
result := acc.Apply(sse.LineResult{
Parsed: true,
Parts: []sse.ContentPart{{Type: "text", Text: "广州天气[citation:1] 多云[reference:0]"}},
})
if !result.ContentSeen {
t.Fatalf("expected marker chunk to mark upstream content")
}
if got := acc.Text.String(); got != "广州天气 多云" {
t.Fatalf("visible text = %q", got)
}
if len(result.Parts) != 1 || result.Parts[0].VisibleText != "广州天气 多云" {
t.Fatalf("unexpected parts: %#v", result.Parts)
}
}