| package sse |
|
|
| import "testing" |
|
|
| |
|
|
| func TestParseDeepSeekSSELineEmptyLine(t *testing.T) { |
| _, _, ok := ParseDeepSeekSSELine([]byte("")) |
| if ok { |
| t.Fatal("expected not parsed for empty line") |
| } |
| } |
|
|
| func TestParseDeepSeekSSELineNoDataPrefix(t *testing.T) { |
| _, _, ok := ParseDeepSeekSSELine([]byte("event: message")) |
| if ok { |
| t.Fatal("expected not parsed for non-data line") |
| } |
| } |
|
|
| func TestParseDeepSeekSSELineInvalidJSON(t *testing.T) { |
| _, _, ok := ParseDeepSeekSSELine([]byte("data: {invalid json")) |
| if ok { |
| t.Fatal("expected not parsed for invalid JSON") |
| } |
| } |
|
|
| func TestParseDeepSeekSSELineWhitespaceOnly(t *testing.T) { |
| _, _, ok := ParseDeepSeekSSELine([]byte(" ")) |
| if ok { |
| t.Fatal("expected not parsed for whitespace-only line") |
| } |
| } |
|
|
| func TestParseDeepSeekSSELineDataWithExtraSpaces(t *testing.T) { |
| chunk, done, ok := ParseDeepSeekSSELine([]byte(`data: {"v":"hello"} `)) |
| if !ok || done { |
| t.Fatalf("expected parsed chunk for spaced data line") |
| } |
| if chunk["v"] != "hello" { |
| t.Fatalf("unexpected chunk: %#v", chunk) |
| } |
| } |
|
|
| |
|
|
| func TestShouldSkipPathQuasiStatus(t *testing.T) { |
| if !shouldSkipPath("response/quasi_status") { |
| t.Fatal("expected skip for quasi_status path") |
| } |
| } |
|
|
| func TestShouldSkipPathPendingFragment(t *testing.T) { |
| if !shouldSkipPath("response/pending_fragment") { |
| t.Fatal("expected skip for pending_fragment path") |
| } |
| } |
|
|
| func TestShouldSkipPathConversationMode(t *testing.T) { |
| if !shouldSkipPath("response/conversation_mode") { |
| t.Fatal("expected skip for conversation_mode path") |
| } |
| } |
|
|
| func TestShouldSkipPathSearchStatus(t *testing.T) { |
| if !shouldSkipPath("response/search_status") { |
| t.Fatal("expected skip for search_status path") |
| } |
| } |
|
|
| func TestShouldSkipPathFragmentStatus(t *testing.T) { |
| if !shouldSkipPath("response/fragments/-1/status") { |
| t.Fatal("expected skip for fragment -1 status") |
| } |
| if !shouldSkipPath("response/fragments/-2/status") { |
| t.Fatal("expected skip for fragment -2 status") |
| } |
| if !shouldSkipPath("response/fragments/-3/status") { |
| t.Fatal("expected skip for fragment -3 status") |
| } |
| if !shouldSkipPath("response/fragments/-16/status") { |
| t.Fatal("expected skip for fragment -16 status") |
| } |
| if !shouldSkipPath("response/fragments/7/status") { |
| t.Fatal("expected skip for fragment 7 status") |
| } |
| if shouldSkipPath("response/status") { |
| t.Fatal("expected response/status to be handled by finish logic, not skipped") |
| } |
| } |
|
|
| func TestShouldSkipPathRegularContent(t *testing.T) { |
| if shouldSkipPath("response/content") { |
| t.Fatal("expected not skip for content path") |
| } |
| if shouldSkipPath("response/thinking_content") { |
| t.Fatal("expected not skip for thinking_content path") |
| } |
| } |
|
|
| |
|
|
| func TestParseSSEChunkForContentNoVField(t *testing.T) { |
| parts, finished, nextType := ParseSSEChunkForContent(map[string]any{"p": "response/content"}, false, "text") |
| if finished { |
| t.Fatal("expected not finished") |
| } |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts when v is missing, got %#v", parts) |
| } |
| if nextType != "text" { |
| t.Fatalf("expected type preserved, got %q", nextType) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentSkippedPath(t *testing.T) { |
| parts, finished, nextType := ParseSSEChunkForContent(map[string]any{ |
| "p": "response/quasi_status", |
| "v": "some data", |
| }, false, "text") |
| if finished || len(parts) > 0 { |
| t.Fatalf("expected skipped path to produce no output") |
| } |
| if nextType != "text" { |
| t.Fatalf("expected type preserved for skipped path") |
| } |
| } |
|
|
| func TestParseSSEChunkForContentFinishedStatus(t *testing.T) { |
| parts, finished, _ := ParseSSEChunkForContent(map[string]any{ |
| "p": "response/status", |
| "v": "FINISHED", |
| }, false, "text") |
| if !finished { |
| t.Fatal("expected finished on status FINISHED") |
| } |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts on finished, got %d", len(parts)) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentStatusNotFinished(t *testing.T) { |
| parts, finished, _ := ParseSSEChunkForContent(map[string]any{ |
| "p": "response/status", |
| "v": "IN_PROGRESS", |
| }, false, "text") |
| if finished { |
| t.Fatal("expected not finished for non-FINISHED status") |
| } |
| if len(parts) != 0 { |
| t.Fatalf("expected non-finished status to be filtered, got %#v", parts) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentEmptyStringV(t *testing.T) { |
| parts, finished, _ := ParseSSEChunkForContent(map[string]any{ |
| "p": "response/content", |
| "v": "", |
| }, false, "text") |
| if finished { |
| t.Fatal("expected not finished") |
| } |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts for empty string v, got %#v", parts) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentFinishedOnEmptyPath(t *testing.T) { |
| parts, finished, _ := ParseSSEChunkForContent(map[string]any{ |
| "p": "", |
| "v": "FINISHED", |
| }, false, "text") |
| if !finished { |
| t.Fatal("expected finished on empty path with FINISHED value") |
| } |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts on finished") |
| } |
| } |
|
|
| func TestParseSSEChunkForContentFinishedOnStatusPath(t *testing.T) { |
| _, finished, _ := ParseSSEChunkForContent(map[string]any{ |
| "p": "status", |
| "v": "FINISHED", |
| }, false, "text") |
| if !finished { |
| t.Fatal("expected finished on status path with FINISHED value") |
| } |
| } |
|
|
| func TestParseSSEChunkForContentThinkingPathEmptyPath(t *testing.T) { |
| parts, _, nextType := ParseSSEChunkForContent(map[string]any{ |
| "v": "some thought", |
| }, true, "thinking") |
| if len(parts) != 1 || parts[0].Type != "thinking" { |
| t.Fatalf("expected thinking part on empty path, got %#v", parts) |
| } |
| if nextType != "thinking" { |
| t.Fatalf("expected nextType thinking, got %q", nextType) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentThinkingEnabledTextType(t *testing.T) { |
| parts, _, nextType := ParseSSEChunkForContent(map[string]any{ |
| "v": "text content", |
| }, true, "text") |
| if len(parts) != 1 || parts[0].Type != "text" { |
| t.Fatalf("expected text part when currentType=text, got %#v", parts) |
| } |
| if nextType != "text" { |
| t.Fatalf("expected nextType text, got %q", nextType) |
| } |
| } |
|
|
| |
|
|
| func TestParseSSEChunkForContentFragmentsAppendThink(t *testing.T) { |
| chunk := map[string]any{ |
| "p": "response/fragments", |
| "o": "APPEND", |
| "v": []any{ |
| map[string]any{ |
| "type": "THINK", |
| "content": "深入思考...", |
| }, |
| }, |
| } |
| parts, finished, nextType := ParseSSEChunkForContent(chunk, true, "text") |
| if finished { |
| t.Fatal("expected not finished") |
| } |
| if nextType != "thinking" { |
| t.Fatalf("expected nextType thinking, got %q", nextType) |
| } |
| if len(parts) != 1 || parts[0].Type != "thinking" || parts[0].Text != "深入思考..." { |
| t.Fatalf("unexpected parts: %#v", parts) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentFragmentsAppendEmptyContent(t *testing.T) { |
| chunk := map[string]any{ |
| "p": "response/fragments", |
| "o": "APPEND", |
| "v": []any{ |
| map[string]any{ |
| "type": "RESPONSE", |
| "content": "", |
| }, |
| }, |
| } |
| parts, finished, nextType := ParseSSEChunkForContent(chunk, true, "thinking") |
| if finished { |
| t.Fatal("expected not finished") |
| } |
| if nextType != "text" { |
| t.Fatalf("expected nextType text, got %q", nextType) |
| } |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts for empty content, got %#v", parts) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentFragmentsAppendDefaultType(t *testing.T) { |
| chunk := map[string]any{ |
| "p": "response/fragments", |
| "o": "APPEND", |
| "v": []any{ |
| map[string]any{ |
| "type": "UNKNOWN", |
| "content": "some text", |
| }, |
| }, |
| } |
| parts, _, _ := ParseSSEChunkForContent(chunk, true, "text") |
| if len(parts) != 1 || parts[0].Type != "text" { |
| t.Fatalf("expected text type for unknown fragment type, got %#v", parts) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentFragmentsAppendNonArray(t *testing.T) { |
| chunk := map[string]any{ |
| "p": "response/fragments", |
| "o": "APPEND", |
| "v": "not an array", |
| } |
| parts, finished, _ := ParseSSEChunkForContent(chunk, true, "text") |
| if finished { |
| t.Fatal("expected not finished") |
| } |
| |
| if len(parts) != 1 || parts[0].Text != "not an array" { |
| t.Fatalf("unexpected parts: %#v", parts) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentFragmentsAppendNonMap(t *testing.T) { |
| chunk := map[string]any{ |
| "p": "response/fragments", |
| "o": "APPEND", |
| "v": []any{"string item"}, |
| } |
| parts, _, _ := ParseSSEChunkForContent(chunk, false, "text") |
| |
| _ = parts |
| } |
|
|
| |
|
|
| func TestParseSSEChunkForContentResponsePathFragmentsAppend(t *testing.T) { |
| chunk := map[string]any{ |
| "p": "response", |
| "v": []any{ |
| map[string]any{ |
| "p": "fragments", |
| "o": "APPEND", |
| "v": []any{ |
| map[string]any{ |
| "type": "THINKING", |
| }, |
| }, |
| }, |
| }, |
| } |
| _, _, nextType := ParseSSEChunkForContent(chunk, true, "text") |
| if nextType != "thinking" { |
| t.Fatalf("expected nextType thinking from response path fragments, got %q", nextType) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentResponsePathResponseFragment(t *testing.T) { |
| chunk := map[string]any{ |
| "p": "response", |
| "v": []any{ |
| map[string]any{ |
| "p": "fragments", |
| "o": "APPEND", |
| "v": []any{ |
| map[string]any{ |
| "type": "RESPONSE", |
| }, |
| }, |
| }, |
| }, |
| } |
| _, _, nextType := ParseSSEChunkForContent(chunk, true, "thinking") |
| if nextType != "text" { |
| t.Fatalf("expected nextType text for RESPONSE fragment, got %q", nextType) |
| } |
| } |
|
|
| |
|
|
| func TestParseSSEChunkForContentMapValueWithFragments(t *testing.T) { |
| chunk := map[string]any{ |
| "v": map[string]any{ |
| "response": map[string]any{ |
| "fragments": []any{ |
| map[string]any{ |
| "type": "THINK", |
| "content": "思考...", |
| }, |
| map[string]any{ |
| "type": "RESPONSE", |
| "content": "回答...", |
| }, |
| }, |
| }, |
| }, |
| } |
| parts, finished, nextType := ParseSSEChunkForContent(chunk, true, "text") |
| if finished { |
| t.Fatal("expected not finished") |
| } |
| if nextType != "text" { |
| t.Fatalf("expected nextType text after RESPONSE, got %q", nextType) |
| } |
| if len(parts) != 2 { |
| t.Fatalf("expected 2 parts, got %d: %#v", len(parts), parts) |
| } |
| if parts[0].Type != "thinking" || parts[0].Text != "思考..." { |
| t.Fatalf("first part mismatch: %#v", parts[0]) |
| } |
| if parts[1].Type != "text" || parts[1].Text != "回答..." { |
| t.Fatalf("second part mismatch: %#v", parts[1]) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentMapValueDirectFragments(t *testing.T) { |
| chunk := map[string]any{ |
| "v": map[string]any{ |
| "fragments": []any{ |
| map[string]any{ |
| "type": "RESPONSE", |
| "content": "直接回答", |
| }, |
| }, |
| }, |
| } |
| parts, _, _ := ParseSSEChunkForContent(chunk, false, "text") |
| if len(parts) != 1 || parts[0].Text != "直接回答" || parts[0].Type != "text" { |
| t.Fatalf("unexpected parts for direct fragments: %#v", parts) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentMapValueUnknownType(t *testing.T) { |
| chunk := map[string]any{ |
| "v": map[string]any{ |
| "fragments": []any{ |
| map[string]any{ |
| "type": "CUSTOM", |
| "content": "custom content", |
| }, |
| }, |
| }, |
| } |
| parts, _, _ := ParseSSEChunkForContent(chunk, false, "text") |
| if len(parts) != 1 || parts[0].Type != "text" { |
| t.Fatalf("expected partType fallback for unknown type, got %#v", parts) |
| } |
| } |
|
|
| func TestParseSSEChunkForContentMapValueEmptyFragmentContent(t *testing.T) { |
| chunk := map[string]any{ |
| "v": map[string]any{ |
| "fragments": []any{ |
| map[string]any{ |
| "type": "RESPONSE", |
| "content": "", |
| }, |
| }, |
| }, |
| } |
| parts, _, _ := ParseSSEChunkForContent(chunk, false, "text") |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts for empty fragment content, got %#v", parts) |
| } |
| } |
|
|
| |
|
|
| func TestParseSSEChunkForContentFragmentContentPathInheritsType(t *testing.T) { |
| chunk := map[string]any{ |
| "p": "response/fragments/-1/content", |
| "v": "继续思考", |
| } |
| parts, _, _ := ParseSSEChunkForContent(chunk, true, "thinking") |
| if len(parts) != 1 || parts[0].Type != "thinking" { |
| t.Fatalf("expected inherited thinking type, got %#v", parts) |
| } |
| } |
|
|
| |
|
|
| func TestIsCitationWithLeadingWhitespace(t *testing.T) { |
| if !IsCitation(" [citation:2] text") { |
| t.Fatal("expected citation true with leading whitespace") |
| } |
| } |
|
|
| func TestIsCitationEmpty(t *testing.T) { |
| if IsCitation("") { |
| t.Fatal("expected citation false for empty string") |
| } |
| } |
|
|
| func TestIsCitationSimilarPrefix(t *testing.T) { |
| if IsCitation("[cite:1] text") { |
| t.Fatal("expected citation false for [cite: prefix") |
| } |
| } |
|
|
| |
|
|
| func TestExtractContentRecursiveFinishedStatus(t *testing.T) { |
| items := []any{ |
| map[string]any{"p": "status", "v": "FINISHED"}, |
| } |
| parts, finished := extractContentRecursive(items, "text") |
| if !finished { |
| t.Fatal("expected finished on status FINISHED") |
| } |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts, got %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveSkipsPath(t *testing.T) { |
| items := []any{ |
| map[string]any{"p": "quasi_status", "v": "data"}, |
| } |
| parts, finished := extractContentRecursive(items, "text") |
| if finished { |
| t.Fatal("expected not finished") |
| } |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts for skipped path, got %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveContentField(t *testing.T) { |
| items := []any{ |
| map[string]any{"p": "x", "v": "val", "content": "actual content", "type": "RESPONSE"}, |
| } |
| parts, _ := extractContentRecursive(items, "text") |
| if len(parts) != 1 || parts[0].Text != "actual content" || parts[0].Type != "text" { |
| t.Fatalf("unexpected parts: %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveContentFieldThinkType(t *testing.T) { |
| items := []any{ |
| map[string]any{"p": "x", "v": "val", "content": "think text", "type": "THINK"}, |
| } |
| parts, _ := extractContentRecursive(items, "text") |
| if len(parts) != 1 || parts[0].Type != "thinking" { |
| t.Fatalf("expected thinking type for THINK content, got %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveThinkingPath(t *testing.T) { |
| items := []any{ |
| map[string]any{"p": "thinking_content", "v": "deep thought"}, |
| } |
| parts, _ := extractContentRecursive(items, "text") |
| if len(parts) != 1 || parts[0].Type != "thinking" || parts[0].Text != "deep thought" { |
| t.Fatalf("unexpected parts for thinking path: %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveContentPath(t *testing.T) { |
| items := []any{ |
| map[string]any{"p": "content", "v": "text content"}, |
| } |
| parts, _ := extractContentRecursive(items, "thinking") |
| if len(parts) != 1 || parts[0].Type != "text" { |
| t.Fatalf("expected text type for content path, got %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveResponsePath(t *testing.T) { |
| items := []any{ |
| map[string]any{"p": "response", "v": "text content"}, |
| } |
| parts, _ := extractContentRecursive(items, "thinking") |
| if len(parts) != 1 || parts[0].Type != "text" { |
| t.Fatalf("expected text type for response path, got %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveFragmentsPath(t *testing.T) { |
| items := []any{ |
| map[string]any{"p": "fragments", "v": "fragment text"}, |
| } |
| parts, _ := extractContentRecursive(items, "thinking") |
| if len(parts) != 1 || parts[0].Type != "text" { |
| t.Fatalf("expected text type for fragments path, got %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveNestedArrayWithTypes(t *testing.T) { |
| items := []any{ |
| map[string]any{ |
| "p": "fragments", |
| "v": []any{ |
| map[string]any{"content": "thought", "type": "THINKING"}, |
| map[string]any{"content": "answer", "type": "RESPONSE"}, |
| "raw string", |
| }, |
| }, |
| } |
| parts, _ := extractContentRecursive(items, "text") |
| if len(parts) != 3 { |
| t.Fatalf("expected 3 parts, got %d: %#v", len(parts), parts) |
| } |
| if parts[0].Type != "thinking" || parts[0].Text != "thought" { |
| t.Fatalf("first part mismatch: %#v", parts[0]) |
| } |
| if parts[1].Type != "text" || parts[1].Text != "answer" { |
| t.Fatalf("second part mismatch: %#v", parts[1]) |
| } |
| if parts[2].Type != "text" || parts[2].Text != "raw string" { |
| t.Fatalf("third part mismatch: %#v", parts[2]) |
| } |
| } |
|
|
| func TestExtractContentRecursiveEmptyContentSkipped(t *testing.T) { |
| items := []any{ |
| map[string]any{ |
| "p": "fragments", |
| "v": []any{ |
| map[string]any{"content": "", "type": "RESPONSE"}, |
| }, |
| }, |
| } |
| parts, _ := extractContentRecursive(items, "text") |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts for empty nested content, got %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveFinishedString(t *testing.T) { |
| items := []any{ |
| map[string]any{"p": "content", "v": "FINISHED"}, |
| } |
| parts, _ := extractContentRecursive(items, "text") |
| |
| if len(parts) != 0 { |
| t.Fatalf("expected FINISHED string to be skipped, got %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveNoVField(t *testing.T) { |
| items := []any{ |
| map[string]any{"p": "content"}, |
| } |
| parts, _ := extractContentRecursive(items, "text") |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts for missing v field, got %#v", parts) |
| } |
| } |
|
|
| func TestExtractContentRecursiveNonMapItem(t *testing.T) { |
| items := []any{"just a string", 42} |
| parts, _ := extractContentRecursive(items, "text") |
| if len(parts) != 0 { |
| t.Fatalf("expected no parts for non-map items, got %#v", parts) |
| } |
| } |
|
|