203 lines
8.6 KiB
Go
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)
|
|
}
|
|
}
|