Files
2026-07-13 13:00:08 +08:00

203 lines
8.6 KiB
Go

package provider
import (
"encoding/json"
"testing"
)
func TestIsMiMoEndpoint(t *testing.T) {
for _, tc := range []struct {
url string
want bool
}{
{"https://api.xiaomimimo.com/v1", true},
{"https://api.xiaomimimo.com/anthropic", true},
{"https://token-plan-cn.xiaomimimo.com/v1", true},
{"https://token-plan-sgp.xiaomimimo.com/anthropic", true},
{"https://token-plan-ams.xiaomimimo.com/v1", true},
{"https://xiaomimimo.com/v1", false},
{"https://api.deepseek.com", false},
{"https://xiaomimimo.com.example.org", false},
{"", false},
{"not-a-url", false},
} {
if got := IsMiMoEndpoint(tc.url); got != tc.want {
t.Errorf("IsMiMoEndpoint(%q) = %v, want %v", tc.url, got, tc.want)
}
}
}
func TestNormalizeLegacyTupleItemsDoesNotRewriteSchemaExamples(t *testing.T) {
raw := json.RawMessage(`{
"type":"object",
"properties":{
"value":{
"type":"object",
"default":{"items":["first","second"]}
}
}
}`)
got := NormalizeLegacyTupleItemsForDraft202012(raw)
var schema map[string]any
if err := json.Unmarshal(got, &schema); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
properties := schema["properties"].(map[string]any)
value := properties["value"].(map[string]any)
defaultValue := value["default"].(map[string]any)
if _, ok := defaultValue["items"].([]any); !ok {
t.Fatalf("schema default was rewritten: %s", got)
}
if _, exists := defaultValue["prefixItems"]; exists {
t.Fatalf("schema default gained prefixItems: %s", got)
}
}
func TestNormalizeLegacyTupleItemsUpdatesRootDialect(t *testing.T) {
raw := json.RawMessage(`{"$schema":"http://json-schema.org/draft-07/schema#","type":"array","items":[{"type":"string"},{"type":"number"}]}`)
var schema map[string]any
if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got := schema["$schema"]; got != "https://json-schema.org/draft/2020-12/schema" {
t.Fatalf("$schema = %v, want 2020-12 after tuple conversion (old declaration would misread prefixItems)", got)
}
if _, ok := schema["prefixItems"].([]any); !ok {
t.Fatalf("prefixItems missing: %v", schema)
}
}
func TestNormalizeLegacyTupleItemsUpdatesNestedResourceDialect(t *testing.T) {
raw := json.RawMessage(`{"type":"object","$defs":{"pair":{"$schema":"https://json-schema.org/draft/2019-09/schema","type":"array","items":[{"type":"string"}]}}}`)
var schema map[string]any
if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if _, exists := schema["$schema"]; exists {
t.Fatalf("root gained a $schema it never declared: %v", schema)
}
pair := schema["$defs"].(map[string]any)["pair"].(map[string]any)
if got := pair["$schema"]; got != "https://json-schema.org/draft/2020-12/schema" {
t.Fatalf("nested resource $schema = %v, want 2020-12", got)
}
if _, ok := pair["prefixItems"].([]any); !ok {
t.Fatalf("nested prefixItems missing: %v", pair)
}
}
func TestNormalizeLegacyTupleItemsLeavesUnconvertedDialectAlone(t *testing.T) {
// No legacy tuple anywhere: the declared old dialect stays, and the exact
// input bytes come back (no reserialization).
raw := json.RawMessage(`{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"xs":{"type":"array","items":{"type":"string"}}}}`)
got := NormalizeLegacyTupleItemsForDraft202012(raw)
if string(got) != string(raw) {
t.Fatalf("unchanged schema was rewritten:\n in: %s\nout: %s", raw, got)
}
}
func TestNormalizeLegacyTupleItemsLeavesUnknownDialectAlone(t *testing.T) {
// An unknown dialect may define its own tuple semantics; a partial rewrite
// (keywords converted, declaration kept) would be self-contradictory, so
// the whole resource comes back byte-identical.
raw := json.RawMessage(`{"$schema":"https://example.com/custom-dialect","type":"array","items":[{"type":"string"}]}`)
if got := NormalizeLegacyTupleItemsForDraft202012(raw); string(got) != string(raw) {
t.Fatalf("custom-dialect resource was rewritten:\n in: %s\nout: %s", raw, got)
}
}
func TestNormalizeLegacyTupleItemsSkipsNestedUnknownDialectResource(t *testing.T) {
// The custom-dialect embedded resource stays untouched while a sibling in
// the (default 2020-12) enclosing schema still converts.
raw := json.RawMessage(`{"type":"object","$defs":{"custom":{"$schema":"https://example.com/custom-dialect","type":"array","items":[{"type":"string"}]},"pair":{"type":"array","items":[{"type":"number"}]}}}`)
var schema map[string]any
if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
defs := schema["$defs"].(map[string]any)
custom := defs["custom"].(map[string]any)
if _, ok := custom["items"].([]any); !ok {
t.Fatalf("custom resource's tuple items were rewritten: %v", custom)
}
if _, exists := custom["prefixItems"]; exists {
t.Fatalf("custom resource gained prefixItems: %v", custom)
}
pair := defs["pair"].(map[string]any)
if _, ok := pair["prefixItems"].([]any); !ok {
t.Fatalf("sibling resource was not converted: %v", pair)
}
}
func TestNormalizeLegacyTupleItemsRepairsExplicit202012(t *testing.T) {
// Array-form items under an explicit 2020-12 declaration is malformed
// input; repairing it keeps the declaration as-is.
raw := json.RawMessage(`{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"array","items":[{"type":"string"}]}`)
var schema map[string]any
if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got := schema["$schema"]; got != "https://json-schema.org/draft/2020-12/schema" {
t.Fatalf("$schema = %v, want 2020-12 kept", got)
}
if _, ok := schema["prefixItems"].([]any); !ok {
t.Fatalf("prefixItems missing: %v", schema)
}
}
func TestNormalizeLegacyTupleItemsCanonicalByteFastPath(t *testing.T) {
// No "items" keyword at all: the very same backing bytes come back with no
// parse — the per-turn cost for the common tool surface must stay zero.
raw := json.RawMessage(`{"type":"object","properties":{"name":{"type":"string"}}}`)
got := NormalizeLegacyTupleItemsForDraft202012(raw)
if len(got) == 0 || &got[0] != &raw[0] {
t.Fatal("schema without items must return the input bytes unchanged")
}
// Conversion is deterministic across calls (no process-global cache holds
// externally supplied schema bytes).
tuple := json.RawMessage(`{"type":"array","items":[{"type":"string"}]}`)
first := NormalizeLegacyTupleItemsForDraft202012(tuple)
second := NormalizeLegacyTupleItemsForDraft202012(tuple)
if string(first) != string(second) {
t.Fatalf("conversion diverged:\n1: %s\n2: %s", first, second)
}
if string(first) == string(tuple) {
t.Fatalf("tuple schema was not converted: %s", first)
}
}
func TestNormalizeLegacyTupleItemsKeepsParentDialectAcrossResourceBoundary(t *testing.T) {
// The nested $defs entry is an independent schema resource (it carries its
// own $schema). Its conversion must reserialize the document but must NOT
// upgrade the parent's untouched 2019-09 declaration — the parent's other
// keywords were written for 2019-09 semantics.
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"}]}}}`)
var schema map[string]any
if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got := schema["$schema"]; got != "https://json-schema.org/draft/2019-09/schema" {
t.Fatalf("parent dialect changed across nested schema-resource boundary: %v", got)
}
pair := schema["$defs"].(map[string]any)["pair"].(map[string]any)
if got := pair["$schema"]; got != "https://json-schema.org/draft/2020-12/schema" {
t.Fatalf("nested resource $schema = %v, want 2020-12 kept", got)
}
if _, ok := pair["prefixItems"].([]any); !ok {
t.Fatalf("nested resource was not converted: %v", pair)
}
}
func TestNormalizeLegacyTupleItemsUpdatesParentForOwnResourceChanges(t *testing.T) {
// Contrast case: the converting subschema declares no $schema of its own,
// so it belongs to the parent's resource and the parent's legacy dialect
// must be updated.
raw := json.RawMessage(`{"$schema":"https://json-schema.org/draft/2019-09/schema","$defs":{"pair":{"type":"array","items":[{"type":"string"}]}}}`)
var schema map[string]any
if err := json.Unmarshal(NormalizeLegacyTupleItemsForDraft202012(raw), &schema); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got := schema["$schema"]; got != "https://json-schema.org/draft/2020-12/schema" {
t.Fatalf("parent dialect = %v, want 2020-12 for a conversion inside its own resource", got)
}
}