返回 DeepSeek-Reasonix
schema_dialect_test.go
根目录 / internal / provider / schema_dialect_test.go
1 package provider
2
3 import (
4 "encoding/json"
5 "testing"
6 )
7
8 func TestIsMiMoEndpoint(t *testing.T) {
9 for _, tc := range []struct {
10 url string
11 want bool
12 }{
13 {"https://api.xiaomimimo.com/v1", true},
14 {"https://api.xiaomimimo.com/anthropic", true},
15 {"https://token-plan-cn.xiaomimimo.com/v1", true},
16 {"https://token-plan-sgp.xiaomimimo.com/anthropic", true},
17 {"https://token-plan-ams.xiaomimimo.com/v1", true},
18 {"https://xiaomimimo.com/v1", false},
19 {"https://api.deepseek.com", false},
20 {"https://xiaomimimo.com.example.org", false},
21 {"", false},
22 {"not-a-url", false},
23 } {
24 if got := IsMiMoEndpoint(tc.url); got != tc.want {
25 t.Errorf("IsMiMoEndpoint(%q) = %v, want %v", tc.url, got, tc.want)
26 }
27 }
28 }
29
30 func TestNormalizeLegacyTupleItemsDoesNotRewriteSchemaExamples(t *testing.T) {
31 raw := json.RawMessage(`{
32 "type":"object",
33 "properties":{
34 "value":{
35 "type":"object",
36 "default":{"items":["first","second"]}
37 }
38 }
39 }`)
40 got := NormalizeLegacyTupleItemsForDraft202012(raw)
41 var schema map[string]any
42 if err := json.Unmarshal(got, &schema); err != nil {
43 t.Fatalf("Unmarshal: %v", err)
44 }
45 properties := schema["properties"].(map[string]any)
46 value := properties["value"].(map[string]any)
47 defaultValue := value["default"].(map[string]any)
48 if _, ok := defaultValue["items"].([]any); !ok {
49 t.Fatalf("schema default was rewritten: %s", got)
50 }
51 if _, exists := defaultValue["prefixItems"]; exists {
52 t.Fatalf("schema default gained prefixItems: %s", got)
53 }
54 }
55
56 func TestNormalizeLegacyTupleItemsUpdatesRootDialect(t *testing.T) {
57 raw := json.RawMessage(`{"$schema":"http://json-schema.org/draft-07/schema#","type":"array","items":[{"type":"string"},{"type":"number"}]}`)
58 var schema map[string]any
59 if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
60 t.Fatalf("Unmarshal: %v", err)
61 }
62 if got := schema["$schema"]; got != "https://json-schema.org/draft/2020-12/schema" {
63 t.Fatalf("$schema = %v, want 2020-12 after tuple conversion (old declaration would misread prefixItems)", got)
64 }
65 if _, ok := schema["prefixItems"].([]any); !ok {
66 t.Fatalf("prefixItems missing: %v", schema)
67 }
68 }
69
70 func TestNormalizeLegacyTupleItemsUpdatesNestedResourceDialect(t *testing.T) {
71 raw := json.RawMessage(`{"type":"object","$defs":{"pair":{"$schema":"https://json-schema.org/draft/2019-09/schema","type":"array","items":[{"type":"string"}]}}}`)
72 var schema map[string]any
73 if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
74 t.Fatalf("Unmarshal: %v", err)
75 }
76 if _, exists := schema["$schema"]; exists {
77 t.Fatalf("root gained a $schema it never declared: %v", schema)
78 }
79 pair := schema["$defs"].(map[string]any)["pair"].(map[string]any)
80 if got := pair["$schema"]; got != "https://json-schema.org/draft/2020-12/schema" {
81 t.Fatalf("nested resource $schema = %v, want 2020-12", got)
82 }
83 if _, ok := pair["prefixItems"].([]any); !ok {
84 t.Fatalf("nested prefixItems missing: %v", pair)
85 }
86 }
87
88 func TestNormalizeLegacyTupleItemsLeavesUnconvertedDialectAlone(t *testing.T) {
89 // No legacy tuple anywhere: the declared old dialect stays, and the exact
90 // input bytes come back (no reserialization).
91 raw := json.RawMessage(`{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"xs":{"type":"array","items":{"type":"string"}}}}`)
92 got := NormalizeLegacyTupleItemsForDraft202012(raw)
93 if string(got) != string(raw) {
94 t.Fatalf("unchanged schema was rewritten:\n in: %s\nout: %s", raw, got)
95 }
96 }
97
98 func TestNormalizeLegacyTupleItemsLeavesUnknownDialectAlone(t *testing.T) {
99 // An unknown dialect may define its own tuple semantics; a partial rewrite
100 // (keywords converted, declaration kept) would be self-contradictory, so
101 // the whole resource comes back byte-identical.
102 raw := json.RawMessage(`{"$schema":"https://example.com/custom-dialect","type":"array","items":[{"type":"string"}]}`)
103 if got := NormalizeLegacyTupleItemsForDraft202012(raw); string(got) != string(raw) {
104 t.Fatalf("custom-dialect resource was rewritten:\n in: %s\nout: %s", raw, got)
105 }
106 }
107
108 func TestNormalizeLegacyTupleItemsSkipsNestedUnknownDialectResource(t *testing.T) {
109 // The custom-dialect embedded resource stays untouched while a sibling in
110 // the (default 2020-12) enclosing schema still converts.
111 raw := json.RawMessage(`{"type":"object","$defs":{"custom":{"$schema":"https://example.com/custom-dialect","type":"array","items":[{"type":"string"}]},"pair":{"type":"array","items":[{"type":"number"}]}}}`)
112 var schema map[string]any
113 if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
114 t.Fatalf("Unmarshal: %v", err)
115 }
116 defs := schema["$defs"].(map[string]any)
117 custom := defs["custom"].(map[string]any)
118 if _, ok := custom["items"].([]any); !ok {
119 t.Fatalf("custom resource's tuple items were rewritten: %v", custom)
120 }
121 if _, exists := custom["prefixItems"]; exists {
122 t.Fatalf("custom resource gained prefixItems: %v", custom)
123 }
124 pair := defs["pair"].(map[string]any)
125 if _, ok := pair["prefixItems"].([]any); !ok {
126 t.Fatalf("sibling resource was not converted: %v", pair)
127 }
128 }
129
130 func TestNormalizeLegacyTupleItemsRepairsExplicit202012(t *testing.T) {
131 // Array-form items under an explicit 2020-12 declaration is malformed
132 // input; repairing it keeps the declaration as-is.
133 raw := json.RawMessage(`{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"array","items":[{"type":"string"}]}`)
134 var schema map[string]any
135 if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
136 t.Fatalf("Unmarshal: %v", err)
137 }
138 if got := schema["$schema"]; got != "https://json-schema.org/draft/2020-12/schema" {
139 t.Fatalf("$schema = %v, want 2020-12 kept", got)
140 }
141 if _, ok := schema["prefixItems"].([]any); !ok {
142 t.Fatalf("prefixItems missing: %v", schema)
143 }
144 }
145
146 func TestNormalizeLegacyTupleItemsCanonicalByteFastPath(t *testing.T) {
147 // No "items" keyword at all: the very same backing bytes come back with no
148 // parse — the per-turn cost for the common tool surface must stay zero.
149 raw := json.RawMessage(`{"type":"object","properties":{"name":{"type":"string"}}}`)
150 got := NormalizeLegacyTupleItemsForDraft202012(raw)
151 if len(got) == 0 || &got[0] != &raw[0] {
152 t.Fatal("schema without items must return the input bytes unchanged")
153 }
154
155 // Conversion is deterministic across calls (no process-global cache holds
156 // externally supplied schema bytes).
157 tuple := json.RawMessage(`{"type":"array","items":[{"type":"string"}]}`)
158 first := NormalizeLegacyTupleItemsForDraft202012(tuple)
159 second := NormalizeLegacyTupleItemsForDraft202012(tuple)
160 if string(first) != string(second) {
161 t.Fatalf("conversion diverged:\n1: %s\n2: %s", first, second)
162 }
163 if string(first) == string(tuple) {
164 t.Fatalf("tuple schema was not converted: %s", first)
165 }
166 }
167
168 func TestNormalizeLegacyTupleItemsKeepsParentDialectAcrossResourceBoundary(t *testing.T) {
169 // The nested $defs entry is an independent schema resource (it carries its
170 // own $schema). Its conversion must reserialize the document but must NOT
171 // upgrade the parent's untouched 2019-09 declaration — the parent's other
172 // keywords were written for 2019-09 semantics.
173 raw := json.RawMessage(`{"$schema":"https://json-schema.org/draft/2019-09/schema","$defs":{"pair":{"$schema":"https://json-schema.org/draft/2020-12/schema","items":[{"type":"string"}]}}}`)
174 var schema map[string]any
175 if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
176 t.Fatalf("Unmarshal: %v", err)
177 }
178 if got := schema["$schema"]; got != "https://json-schema.org/draft/2019-09/schema" {
179 t.Fatalf("parent dialect changed across nested schema-resource boundary: %v", got)
180 }
181 pair := schema["$defs"].(map[string]any)["pair"].(map[string]any)
182 if got := pair["$schema"]; got != "https://json-schema.org/draft/2020-12/schema" {
183 t.Fatalf("nested resource $schema = %v, want 2020-12 kept", got)
184 }
185 if _, ok := pair["prefixItems"].([]any); !ok {
186 t.Fatalf("nested resource was not converted: %v", pair)
187 }
188 }
189
190 func TestNormalizeLegacyTupleItemsUpdatesParentForOwnResourceChanges(t *testing.T) {
191 // Contrast case: the converting subschema declares no $schema of its own,
192 // so it belongs to the parent's resource and the parent's legacy dialect
193 // must be updated.
194 raw := json.RawMessage(`{"$schema":"https://json-schema.org/draft/2019-09/schema","$defs":{"pair":{"type":"array","items":[{"type":"string"}]}}}`)
195 var schema map[string]any
196 if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
197 t.Fatalf("Unmarshal: %v", err)
198 }
199 if got := schema["$schema"]; got != "https://json-schema.org/draft/2020-12/schema" {
200 t.Fatalf("parent dialect = %v, want 2020-12 for a conversion inside its own resource", got)
201 }
202 }
203
203 lines GO