package lang import ( "os" "path/filepath" "strings" "testing" ) // realRoot is the in-repo langpack root, relative to this package's test cwd (internal/lang/). const realRoot = "../../configs/langpacks" // TestLoadResolvesRealZhRu loads the REAL zh→ru pack from disk and pins a spot-check of the moved data // (the const→file move must be byte-faithful; the full-book parity that pins the exact miner output is // the stand-gated TestMinerFullBookParity, so this is the CI-runnable anchor). It exercises the real // disk-loading path end to end. func TestLoadResolvesRealZhRu(t *testing.T) { p, err := Load(realRoot, "zh", "ru") if err != nil { t.Fatalf("Load(zh,ru): %v", err) } if p.Pair != "zh-ru" { t.Errorf("Pair = %q, want zh-ru", p.Pair) } // Source morphology spot-check (byte-faithful move). if !p.SurnamesSingle['赵'] || !p.SurnamesSingle['方'] { t.Error("surnames-single missing 赵/方") } if p.SurnamesSingle['凝'] { t.Error("surnames-single must NOT contain 凝 (discarded — not a surname)") } if !p.SurnamesCompound["古月"] || !p.SurnamesCompound["欧阳"] { t.Error("surnames-compound missing 古月/欧阳") } if len(p.TitleSuffix) == 0 || p.TitleSuffix[0] != "公子" { t.Errorf("title-suffix order not preserved: %v", p.TitleSuffix) } if !p.TopoSuffix['山'] || !p.Numeral['三'] || !p.GradePrefix['甲'] || !p.AliasParticle['的'] { t.Error("rune-set membership missing an expected char (山/三/甲/的)") } // Pair transliteration spot-check. if p.PalladiusInitials["b"] != "б" || p.PalladiusInitials["zh"] != "чж" { t.Errorf("palladius initials wrong: b=%q zh=%q", p.PalladiusInitials["b"], p.PalladiusInitials["zh"]) } if p.PalladiusSpecialI["zhi"] != "чжи" { t.Errorf("palladius special_i zhi = %q, want чжи", p.PalladiusSpecialI["zhi"]) } if !strings.HasPrefix(p.Version(), packAlgoVersion+"-") { t.Errorf("Version() = %q, want %s-", p.Version(), packAlgoVersion) } } // TestRoutesByPairToDifferentBytes is the load-bearing generality proof: a SECOND pair, dropped as data // (no pipeline/ edit, no recompile), routes to DIFFERENT bytes and a DIFFERENT version. Proves "add a // language = data only" — the pair is a genuine routing key, not a baked-in zh constant. func TestRoutesByPairToDifferentBytes(t *testing.T) { root := t.TempDir() writeSyntheticPack(t, root, "xx", "yy") real, err := Load(realRoot, "zh", "ru") if err != nil { t.Fatalf("load real: %v", err) } synth, err := Load(root, "xx", "yy") if err != nil { t.Fatalf("load synthetic xx-yy: %v", err) } // The synthetic pair carries its OWN surnames (not zh's), proving the resolver routes by pair, not // to a hardcoded set. if !synth.SurnamesSingle['甴'] { t.Error("synthetic pack must carry its own surname 甴") } if synth.SurnamesSingle['赵'] { t.Error("synthetic pack must NOT inherit zh's surname 赵 (would mean a baked-in constant)") } if !real.SurnamesSingle['赵'] || real.SurnamesSingle['甴'] { t.Error("real zh pack routing leaked/borrowed synthetic bytes") } if real.Version() == synth.Version() { t.Errorf("different-byte packs must have different versions (real=%s synth=%s)", real.Version(), synth.Version()) } } // TestFailsLoudOnMissingPair pins the fail-loud contract: a pair with no pack directory errors, naming the // missing file — never a silent empty pack (mirrors the prompt seam's PromptPathFor fail-loud). This is // the load-time invariant a live consumer relies on (fail before billing). func TestFailsLoudOnMissingPair(t *testing.T) { _, err := Load(realRoot, "ja", "ru") if err == nil { t.Fatal("Load(ja,ru) must fail loud (no ja langpack), got nil error") } if !strings.Contains(err.Error(), "ja-ru") { t.Errorf("error must name the missing pair ja-ru, got: %v", err) } } // TestFailsLoudOnEmptyTable pins the "never silently empty" contract: a present-but-empty (comment-only) // required file is a corrupt pack and must fail loud at load — not parse to an empty set that silently // disables a miner channel once packs are hand-authored (R1). func TestFailsLoudOnEmptyTable(t *testing.T) { root := t.TempDir() writeSyntheticPack(t, root, "xx", "yy") // Blank out one required table (keep the file present, drop its content). if err := os.WriteFile(filepath.Join(root, "xx", "surnames-single.txt"), []byte("# emptied by a bad edit\n"), 0o644); err != nil { t.Fatal(err) } _, err := Load(root, "xx", "yy") if err == nil { t.Fatal("an empty required table must fail loud, got nil error") } if !strings.Contains(err.Error(), "surnames-single") { t.Errorf("error must name the empty table, got: %v", err) } } // writeSyntheticPack writes a minimal, VALID pack for (src, tgt) under root with bytes distinct from zh — // enough for Load to succeed and for the routing assertion to bite. func writeSyntheticPack(t *testing.T, root, src, tgt string) { t.Helper() pair := src + "-" + tgt files := map[string]string{ filepath.Join(src, "surnames-single.txt"): "# synthetic\n甴甶甹\n", filepath.Join(src, "surnames-compound.txt"): "# synthetic\n甲乙\n", filepath.Join(src, "title-suffix.txt"): "# synthetic\n阁下\n", filepath.Join(src, "ordinal-title.txt"): "# synthetic\n第甲\n", filepath.Join(src, "rank-word.txt"): "# synthetic\n級\n", filepath.Join(src, "topo-suffix.txt"): "# synthetic\n峰\n", filepath.Join(src, "grade-prefix.txt"): "# synthetic\n子丑\n", filepath.Join(src, "numeral.txt"): "# synthetic\n壹貳\n", filepath.Join(src, "alias-particle.txt"): "# synthetic\n之乎\n", filepath.Join(pair, "palladius.txt"): "# synthetic\ninitials\tb\tб\nfinals\ta\tа\nyw\tyi\tи\nspecial_i\tzhi\tчжи\n", } for rel, body := range files { p := filepath.Join(root, rel) if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(p, []byte(body), 0o644); err != nil { t.Fatal(err) } } }