Files
gronod eab16991ac Implement the runnable MCP server: 31 tools, auth, projections (Phase 07)
Lands the executable half of the Phase 01-06 contracts: stdio MCP
wiring with bundle policy enforced at call time, JWT/api_key upstream
auth with single-flight renewal and 401 retry, strict argument
decoding, allowlisted result projections with enum label twins, TV
season expansion, settings read-modify-write under a revision lock,
and a sanitized ToolError envelope that never forwards raw upstream
bodies.
2026-09-18 19:14:10 +01:00

113 lines
2.8 KiB
Go

package tools
import (
"encoding/json"
"testing"
)
func TestRegistryHas31UniqueTools(t *testing.T) {
reg := Registry()
if len(reg) != 31 {
t.Fatalf("registry has %d tools, want 31", len(reg))
}
seen := map[string]bool{}
for _, d := range reg {
if seen[d.Name] {
t.Errorf("duplicate tool %q", d.Name)
}
seen[d.Name] = true
if d.Handler == nil {
t.Errorf("tool %q has no handler", d.Name)
}
if d.Bundle != "core" && d.Bundle != "moderation" && d.Bundle != "administration" {
t.Errorf("tool %q has unknown bundle %q", d.Name, d.Bundle)
}
}
}
func TestBundleCounts(t *testing.T) {
counts := map[string]int{}
for _, d := range Registry() {
counts[d.Bundle]++
}
if counts["core"] != 18 || counts["moderation"] != 5 || counts["administration"] != 8 {
t.Errorf("bundle counts = %v, want core=18 moderation=5 administration=8", counts)
}
}
func TestEveryToolHasInputSchema(t *testing.T) {
for _, d := range Registry() {
s, err := d.InputSchema()
if err != nil {
t.Errorf("%s: %v", d.Name, err)
continue
}
m, ok := s.(map[string]any)
if !ok || m["type"] != "object" {
t.Errorf("%s: input schema is not a JSON object schema", d.Name)
}
}
}
func TestEveryToolHasComposedOutputSchema(t *testing.T) {
for _, d := range Registry() {
s, err := d.OutputSchema()
if err != nil {
t.Errorf("%s: %v", d.Name, err)
continue
}
m := s.(map[string]any)
props := m["properties"].(map[string]any)
if props["operation"].(map[string]any)["const"] != d.Name {
t.Errorf("%s: operation const mismatch", d.Name)
}
defs := m["$defs"].(map[string]any)
if _, ok := defs["error"]; !ok {
t.Errorf("%s: output schema lacks error def", d.Name)
}
for _, f := range d.Families {
if _, ok := defs[f]; !ok {
t.Errorf("%s: output schema lacks family def %q", d.Name, f)
}
}
// Every #/$defs ref inside the composed schema must resolve.
for _, ref := range findRefs(m) {
if _, ok := defs[ref]; !ok {
t.Errorf("%s: unresolved $defs ref %q", d.Name, ref)
}
}
}
}
func TestEnabledFiltering(t *testing.T) {
coreOnly := map[string]bool{"core": true}
n := 0
for _, d := range Registry() {
if d.Enabled(coreOnly) {
n++
}
}
if n != 18 {
t.Errorf("core-only registry = %d tools, want 18", n)
}
}
// TestToolResultEnvelopeJSON verifies the wire field names.
func TestToolResultEnvelopeJSON(t *testing.T) {
res := &ToolResult{OK: true, Operation: "read_search",
Warnings: []string{}, CorrelationID: "abc"}
raw, err := json.Marshal(res)
if err != nil {
t.Fatal(err)
}
var m map[string]any
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
for _, k := range []string{"ok", "operation", "warnings", "truncated", "correlation_id"} {
if _, ok := m[k]; !ok {
t.Errorf("envelope missing key %q", k)
}
}
}