From bb7c460d123a7164d631341f8ad88e80900e97d8 Mon Sep 17 00:00:00 2001 From: claude Date: Mon, 7 Sep 2026 01:43:23 +0400 Subject: [PATCH] router/semantic: tests for contract, mapping, corpus, and split (slice 12) --- internal/router/semantic/contract_test.go | 125 ++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 internal/router/semantic/contract_test.go diff --git a/internal/router/semantic/contract_test.go b/internal/router/semantic/contract_test.go new file mode 100644 index 0000000..5001710 --- /dev/null +++ b/internal/router/semantic/contract_test.go @@ -0,0 +1,125 @@ +package semantic + +import ( + "testing" + + "github.com/kami/maven/internal/router" +) + +func TestValidRoute(t *testing.T) { + for _, r := range AllRoutes { + if !ValidRoute(r) { + t.Errorf("ValidRoute(%q) = false, want true", r) + } + } + if ValidRoute("bogus") { + t.Error("ValidRoute(\"bogus\") = true, want false") + } +} + +func TestIntentToRouteMapping(t *testing.T) { + cases := []struct { + intent router.Intent + route SemanticRoute + }{ + {router.IntentChat, RouteConversation}, + {router.IntentQuery, RouteKnowledge}, + {router.IntentAct, RouteAction}, + {router.IntentReminder, RouteAction}, + {router.IntentFact, RouteMemoryWrite}, + {router.IntentNote, RouteMemoryWrite}, + {router.IntentSystem, RouteSystem}, + {router.Intent("unknown"), RouteUncertain}, + {router.Intent(""), RouteUncertain}, + } + for _, c := range cases { + got := IntentToRoute(c.intent) + if got != c.route { + t.Errorf("IntentToRoute(%q) = %q, want %q", c.intent, got, c.route) + } + } +} + +func TestLoadCorpus(t *testing.T) { + exs, err := LoadCorpus() + if err != nil { + t.Fatal(err) + } + if len(exs) == 0 { + t.Fatal("corpus is empty") + } + for _, e := range exs { + if !ValidRoute(e.Route) { + t.Errorf("example %q has invalid route %q", e.SourceID, e.Route) + } + if e.Source == "" { + t.Errorf("example %q has empty source", e.SourceID) + } + if e.SplitGroup == "" { + t.Errorf("example %q has empty split_group", e.SourceID) + } + } + fastPath, residual := SplitCounts(exs) + if fastPath == 0 { + t.Error("no fast_path_resolved examples in corpus") + } + if residual == 0 { + t.Error("no residual examples in corpus") + } + t.Logf("corpus: %d examples (%d fast-path, %d residual)", len(exs), fastPath, residual) +} + +func TestCorpusByRoute(t *testing.T) { + exs, err := LoadCorpus() + if err != nil { + t.Fatal(err) + } + byRoute := ByRoute(exs) + for _, route := range AllRoutes { + count := len(byRoute[route]) + if count == 0 { + t.Errorf("no examples for route %q", route) + } + t.Logf(" %s: %d examples", route, count) + } +} + +func TestSplitByFamily(t *testing.T) { + exs, err := LoadCorpus() + if err != nil { + t.Fatal(err) + } + train, eval := SplitByFamily(exs, 0.8) + total := len(train) + len(eval) + if total != len(exs) { + t.Errorf("split lost examples: train=%d eval=%d total=%d, want %d", + len(train), len(eval), total, len(exs)) + } + trainGroups := map[string]bool{} + for _, e := range train { + trainGroups[e.SplitGroup] = true + } + for _, e := range eval { + if trainGroups[e.SplitGroup] { + t.Errorf("split_group %q leaked across train/eval (example %q)", + e.SplitGroup, e.SourceID) + } + } + t.Logf("split: %d train, %d eval", len(train), len(eval)) +} + +func TestFamilyID(t *testing.T) { + id1 := FamilyID("ru-act-002", "negation") + id2 := FamilyID("ru-act-002", "question") + id3 := FamilyID("ru-act-002", "") + id4 := FamilyID("ru-act-001", "negation") + if id1 == id2 { + t.Error("different transforms produced same FamilyID") + } + if id1 == id3 { + t.Error("transform and base produced same FamilyID") + } + if id1 == id4 { + t.Error("different base IDs produced same FamilyID") + } +}