package main import ( "encoding/json" "os" "strings" "testing" "github.com/kami/maven/internal/morph" ) func TestEvaluateFixtures(t *testing.T) { for _, fx := range Fixtures { got := Evaluate(fx.Utterance) if got.Eligibility != fx.Want { t.Errorf("%s: want %s got %s (reasons %v)", fx.Utterance, fx.Want, got.Eligibility, got.Reasons) } } } // TestMeasuredDiscriminators pins the corpus-verified numbers on the frozen // pool. These are the exact measurements the rules were built on, so a change // that moves them is a rule regression visible in the slice. func loadPool(t *testing.T) []Row { t.Helper() b, err := os.ReadFile("/tmp/mvn-s21/pool.json") if os.IsNotExist(err) { t.Skip("pool.json missing; run slice21_emit.py first") } if err != nil { t.Fatal(err) } var rows []Row if err := json.Unmarshal(b, &rows); err != nil { t.Fatal(err) } return rows } func TestDevCapabilityProhibition(t *testing.T) { if !morph.Available() { t.Skip("morph dict unavailable") } rows := loadPool(t) var n, pass, blockedN, ambig int for _, r := range rows { if !hasTok(r.Tags, "capability_question") { continue } n++ switch Evaluate(variants(r.NText)[vOrig]).Eligibility { case Permissive: pass++ case Blocked: blockedN++ case Ambiguous: ambig++ } } if n != 126 { t.Fatalf("capability-question rows = %d, want 126", n) } if pass != 0 { t.Fatalf("capability-question dangerous pass = %d, want 0", pass) } if blockedN != 126 && ambig != 0 { t.Errorf("blocked=%d ambig=%d, expect all 126 blocked", blockedN, ambig) } } func TestDevBareCanPoliteIsAction(t *testing.T) { if !morph.Available() { t.Skip("morph dict unavailable") } rows := loadPool(t) total, action, perm := 0, 0, 0 for _, r := range rows { toks := tokens(r.NText) if !hasTok(toks, "можешь") || hasAny(toks, ruAddress) || hasAny(toks, append([]string{}, ruAbilityForms.single...)) { continue } total++ if r.Route == "action" { action++ } if Evaluate(r.NText).Eligibility == Permissive { perm++ } } if total != 127 || action != 127 { t.Fatalf("bare-можешь+polite: n=%d action=%d, want 127/127", total, action) } if perm != 127 { t.Fatalf("bare-можешь+polite permissive=%d, want 127", perm) } } func TestDevEnglishCanPoliteIsAction(t *testing.T) { if !morph.Available() { t.Skip("morph dict unavailable") } rows := loadPool(t) total, action, perm := 0, 0, 0 for _, r := range rows { toks := tokens(r.NText) if !hasAny(toks, enCanForms) { continue } total++ if r.Route == "action" { action++ } if Evaluate(r.NText).Eligibility == Permissive { perm++ } } if total != 96 || action != 96 { t.Fatalf("can-rows: n=%d action=%d, want 96/96", total, action) } if perm != 96 { t.Fatalf("can-rows permissive=%d, want 96", perm) } } func TestNoPermissiveRowEndsInQuestion(t *testing.T) { // asymmetric posture: the guard must never approve a trailing-? frame -- // the corpus has 0 action rows ending in "?", and approving any would // bet on punctuation the slice has ruled uncertain. rows := loadPool(t) for _, r := range rows { if !strings.HasSuffix(r.NText, "?") { continue } if got := Evaluate(r.NText).Eligibility; got == Permissive { t.Errorf("canonical '?': %q approved (%s)", r.Text, got) } } }