返回 DeepSeek-Reasonix
reasoning_replay_error_test.go
根目录 / internal / provider / reasoning_replay_error_test.go
1 package provider
2
3 import (
4 "errors"
5 "fmt"
6 "testing"
7 )
8
9 func TestParseReasoningReplayError(t *testing.T) {
10 cases := []struct {
11 name string
12 status int
13 body string
14 want bool
15 }{
16 {
17 name: "deepseek anthropic thinking 400",
18 status: 400,
19 body: `{"error":{"message":"The ` + "`content[].thinking`" + ` in the thinking mode must be passed back to the API","type":"invalid_request_error"}}`,
20 want: true,
21 },
22 {
23 name: "openai-style reasoning_content 400",
24 status: 400,
25 body: `{"error":{"message":"reasoning_content must be passed back to the API in thinking mode"}}`,
26 want: true,
27 },
28 {
29 name: "official responses reasoning_text 400",
30 status: 400,
31 body: "The `reasoning_text` in the thinking mode must be passed back to the API.",
32 want: true,
33 },
34 {
35 name: "responses unrelated reasoning error",
36 status: 400,
37 body: "reasoning_text is not supported",
38 want: false,
39 },
40 {
41 name: "case-insensitive",
42 status: 400,
43 body: `The CONTENT[].THINKING in the thinking mode MUST BE PASSED BACK to the API`,
44 want: true,
45 },
46 {
47 name: "400 without pass-back obligation",
48 status: 400,
49 body: `{"error":{"message":"content[].thinking is not supported by this model"}}`,
50 want: false,
51 },
52 {
53 name: "400 pass-back without reasoning reference",
54 status: 400,
55 body: `{"error":{"message":"the signed block must be passed back unchanged"}}`,
56 want: false,
57 },
58 {
59 name: "401 with the same body",
60 status: 401,
61 body: `{"error":{"message":"The content[].thinking in the thinking mode must be passed back to the API"}}`,
62 want: false,
63 },
64 {
65 name: "422 with the same body",
66 status: 422,
67 body: `{"error":{"message":"The content[].thinking in the thinking mode must be passed back to the API"}}`,
68 want: false,
69 },
70 {
71 name: "unrelated 400",
72 status: 400,
73 body: `{"error":{"message":"invalid tool schema at index 2"}}`,
74 want: false,
75 },
76 {
77 name: "empty body",
78 status: 400,
79 body: "",
80 want: false,
81 },
82 }
83 for _, tc := range cases {
84 t.Run(tc.name, func(t *testing.T) {
85 got := ParseReasoningReplayError(&APIError{Provider: "p", Status: tc.status, Body: tc.body})
86 if (got != nil) != tc.want {
87 t.Fatalf("ParseReasoningReplayError = %v, want match=%v", got, tc.want)
88 }
89 if got != nil && got.APIError == nil {
90 t.Fatal("matched error lost the underlying APIError")
91 }
92 })
93 }
94 if ParseReasoningReplayError(nil) != nil {
95 t.Fatal("nil APIError must not match")
96 }
97 }
98
99 func TestAsReasoningReplayError(t *testing.T) {
100 apiErr := &APIError{Provider: "p", Status: 400, Body: "The `content[].thinking` in the thinking mode must be passed back to the API"}
101 replay := ParseReasoningReplayError(apiErr)
102 if replay == nil {
103 t.Fatal("fixture did not match")
104 }
105 if got := AsReasoningReplayError(fmt.Errorf("stream: %w", replay)); got != replay {
106 t.Fatalf("AsReasoningReplayError = %v, want the wrapped error", got)
107 }
108 if got := AsReasoningReplayError(apiErr); got != nil {
109 t.Fatalf("bare APIError must not unwrap as ReasoningReplayError: %v", got)
110 }
111 if got := AsReasoningReplayError(errors.New("other")); got != nil {
112 t.Fatalf("unrelated error = %v, want nil", got)
113 }
114 if got := AsReasoningReplayError(nil); got != nil {
115 t.Fatal("nil error must not match")
116 }
117 if !errors.Is(replay, apiErr) {
118 t.Fatal("ReasoningReplayError must unwrap to the APIError")
119 }
120 }
121
121 lines GO