package mcp import ( "testing" ) // TestSchemaCache_PutGetInvalidate 覆盖缓存基本语义与按服务器失效。 func TestSchemaCache_PutGetInvalidate(t *testing.T) { t.Parallel() cache := NewSchemaCache() schema := map[string]any{"type": "object", "properties": map[string]any{"a": map[string]any{"type": "number"}}} if err := cache.Put("s1", "tool", schema); err != nil { t.Fatalf("Put: %v", err) } if err := cache.Put("s2", "tool", schema); err != nil { t.Fatalf("Put(s2): %v", err) } if _, ok := cache.Get("s1", "tool"); !ok { t.Fatal("Get(s1,tool) 未命中") } if cache.Len() != 2 { t.Fatalf("Len = %d, 期望 2", cache.Len()) } // 只失效 s1,s2 的同名工具必须留下。 cache.Invalidate("s1") if _, ok := cache.Get("s1", "tool"); ok { t.Fatal("Invalidate(s1) 后仍然命中") } if _, ok := cache.Get("s2", "tool"); !ok { t.Fatal("Invalidate(s1) 误删了 s2 的缓存") } cache.Clear() if cache.Len() != 0 { t.Fatalf("Clear 后 Len = %d, 期望 0", cache.Len()) } } // TestSchemaCache_Prepare_AppliesDefaultsAndValidates 命中缓存时补默认值并校验。 func TestSchemaCache_Prepare_AppliesDefaultsAndValidates(t *testing.T) { t.Parallel() cache := NewSchemaCache() schema := map[string]any{ "type": "object", "properties": map[string]any{ "name": map[string]any{"type": "string", "default": "world"}, "n": map[string]any{"type": "number"}, }, "required": []string{"n"}, } if err := cache.Put("s", "t", schema); err != nil { t.Fatalf("Put: %v", err) } out, err := cache.Prepare("s", "t", nil, map[string]any{"n": 1}) if err != nil { t.Fatalf("Prepare: %v", err) } if out["name"] != "world" { t.Fatalf("默认值未生效:%v", out) } if _, err := cache.Prepare("s", "t", nil, map[string]any{"n": "x"}); err == nil { t.Fatal("类型错误的参数没有被拦下") } if _, err := cache.Prepare("s", "t", nil, map[string]any{}); err == nil { t.Fatal("缺必填参数没有被拦下") } } // TestSchemaCache_Prepare_HitsCacheWithoutReparsing 证明「命中缓存」这条要求: // 发现阶段已缓存 → 后续调用全是命中,未命中计数为 0(没有重复拉取/解析)。 func TestSchemaCache_Prepare_HitsCacheWithoutReparsing(t *testing.T) { t.Parallel() cache := NewSchemaCache() if err := cache.Put("s", "t", emptyObjectSchema()); err != nil { t.Fatalf("Put: %v", err) } for i := 0; i < 3; i++ { if _, err := cache.Prepare("s", "t", nil, map[string]any{}); err != nil { t.Fatalf("Prepare #%d: %v", i, err) } } hits, misses := cache.Stats() if hits != 3 || misses != 0 { t.Fatalf("Stats = (%d hits, %d misses), 期望 (3, 0)", hits, misses) } } // TestSchemaCache_Prepare_MissFillsFromFallback 缓存未命中时用发现结果兜底填入, // 且失效后第一次调用计一次未命中。 func TestSchemaCache_Prepare_MissFillsFromFallback(t *testing.T) { t.Parallel() cache := NewSchemaCache() fallback := map[string]any{ "type": "object", "properties": map[string]any{ "name": map[string]any{"type": "string", "default": "fallback"}, }, } out, err := cache.Prepare("s", "t", fallback, map[string]any{}) if err != nil { t.Fatalf("Prepare: %v", err) } if out["name"] != "fallback" { t.Fatalf("兜底 schema 的默认值未生效:%v", out) } if _, misses := cache.Stats(); misses != 1 { t.Fatalf("misses = %d, 期望 1", misses) } if cache.Len() != 1 { t.Fatalf("未命中后没有回填缓存:Len = %d", cache.Len()) } cache.Invalidate("s") if _, err := cache.Prepare("s", "t", nil, map[string]any{"x": 1}); err != nil { t.Fatalf("无 schema 时应降级为不校验:%v", err) } } // TestSchemaCache_Put_BadSchemaDegrades 无法解析的 schema 只降级为「不校验」,不阻断调用。 func TestSchemaCache_Put_BadSchemaDegrades(t *testing.T) { t.Parallel() cache := NewSchemaCache() bad := map[string]any{"type": "object", "properties": map[string]any{"a": map[string]any{"$ref": "#/$defs/does-not-exist"}}} err := cache.Put("s", "t", bad) if err == nil { t.Fatal("病态 schema 未报错,期望解析失败可见") } if _, ok := cache.Get("s", "t"); !ok { t.Fatal("解析失败时原始 schema 没有缓存下来(应降级保留)") } if _, err := cache.Prepare("s", "t", nil, map[string]any{"whatever": true}); err != nil { t.Fatalf("降级路径不应报错:%v", err) } } // TestSchemaCache_Prepare_NilSchemaSafe nil schema 走空对象兜底,不 panic。 func TestSchemaCache_Prepare_NilSchemaSafe(t *testing.T) { t.Parallel() cache := NewSchemaCache() if err := cache.Put("s", "t", nil); err != nil { t.Fatalf("Put(nil): %v", err) } if _, err := cache.Prepare("s", "t", nil, nil); err != nil { t.Fatalf("Prepare(nil args): %v", err) } }