返回 DeepSeek-Reasonix
stream_scanner_test.go
根目录 / internal / provider / stream_scanner_test.go
1 package provider
2
3 import (
4 "encoding/json"
5 "strings"
6 "testing"
7 )
8
9 func TestStreamScannerDistinguishesTruncationFromMalformedEvents(t *testing.T) {
10 for _, tc := range []struct {
11 name, line string
12 interrupted bool
13 }{
14 {"cut string", `data: {"delta":"partial`, true},
15 {"cut literal", `data: {"delta":nu`, true},
16 {"malformed complete line", "data: {\"delta\":nu\n", false},
17 {"malformed complete CRLF", "data: {\"delta\":nu\r\n", false},
18 {"malformed earlier field", `data: {oops}`, false},
19 {"html gateway reply", "data: <html>bad gateway</html>", false},
20 } {
21 t.Run(tc.name, func(t *testing.T) {
22 s := NewStreamScanner(strings.NewReader("event: delta\n"+tc.line), 1024*1024)
23 for range 2 {
24 if !s.Scan() {
25 t.Fatal("missing lines")
26 }
27 }
28 payload := strings.TrimSpace(strings.TrimPrefix(s.Text(), "data:"))
29 var v any
30 err := json.Unmarshal([]byte(payload), &v)
31 if err == nil {
32 t.Fatal("fixture must fail decode")
33 }
34 got := s.DecodeError("fixture", payload, err)
35 if IsStreamInterrupted(got) != tc.interrupted {
36 t.Fatalf("interrupted=%v error=%v", IsStreamInterrupted(got), got)
37 }
38 if tc.interrupted && !ClassifyRecovery(got).Retryable {
39 t.Fatal("cut stream is not retryable")
40 }
41 })
42 }
43 }
44
45 func TestStreamScannerCompleteTerminalWithoutNewlineStillDecodes(t *testing.T) {
46 s := NewStreamScanner(strings.NewReader(`data: {"finish_reason":"stop"}`), 1024*1024)
47 if !s.Scan() {
48 t.Fatal("missing final line")
49 }
50 var value any
51 if err := json.Unmarshal([]byte(strings.TrimPrefix(s.Text(), "data: ")), &value); err != nil {
52 t.Fatal(err)
53 }
54 if s.Scan() || s.Err() != nil {
55 t.Fatal("unexpected extra line or error")
56 }
57 }
58
58 lines GO