package lang import ( "strings" "testing" ) // TestEmbeddedVersionMutation is the П0 acceptance test (D39.60 §6.1, "bytes ARE version" for the embed // plane): a byte edit of an embedded data file MUST move EmbeddedVersion — so, once it is folded into the // snapshot, an edit is a loud --resnapshot, never a silent verdict/wire change. It also pins the format and // reproducibility (the same bytes always hash the same). func TestEmbeddedVersionMutation(t *testing.T) { got := EmbeddedVersion() if !strings.HasPrefix(got, embedAlgoVersion+"-") { t.Fatalf("EmbeddedVersion() = %q, want %s-", got, embedAlgoVersion) } files := embeddedDataFiles() if len(files) == 0 { t.Fatal("no embedded data files") } if base := hashEmbeddedFiles(files); base != got { t.Fatalf("EmbeddedVersion not reproducible from embeddedDataFiles(): %s != %s", base, got) } // Mutate ONE byte of a verdict-bearing file (target-ru.txt drives the sanitizer) and a wire-bearing file // (injection.txt rides the request) — each must diverge the hash on its own. for _, target := range []string{"target-ru.txt", "injection.txt"} { mutated := make([]embeddedFile, len(files)) copy(mutated, files) var found bool for i := range mutated { if mutated[i].name == target { b := append([]byte(nil), mutated[i].data...) b[len(b)/2] ^= 0x01 // flip a content byte mutated[i].data = b found = true break } } if !found { t.Fatalf("%s is not embedded", target) } if mut := hashEmbeddedFiles(mutated); mut == got { t.Errorf("a byte edit of %s did NOT move EmbeddedVersion (%s) — the embed plane would drift silently", target, mut) } } } // TestTheInjectionKeysAreOneVocabulary pins the property the key table exists for: the keys the PARSER // accepts and the keys a guard may REQUIRE are the same set, read from one place. A guard naming its own // literals would pass forever on a key the file stopped carrying, and the run it was supposed to refuse — // a bank its target cannot state — is silent by construction. func TestTheInjectionKeysAreOneVocabulary(t *testing.T) { // Every key the parser accepts must be answerable by Authored. A key that parses into a field nothing // can read back is a row a guard cannot see. for key := range injectionFields { parsed, err := parseInjection([]byte("xx\t" + key + "\tзначение")) if err != nil { t.Fatalf("the parser accepts %q but this file could not be parsed: %v", key, err) } if !parsed["xx"].Authored(key) { t.Errorf("key %q parses into a field Authored cannot read back — a guard asking for it would never see the row", key) } if parsed["xx"].Authored("no_such_key") { t.Errorf("an unknown key must never read as authored (%q)", key) } } if _, err := parseInjection([]byte("xx\tno_such_key\tзначение")); err == nil { t.Error("an unknown injection key must fail the parse loudly — a typo that parses is a row nobody renders") } } // TestAPartialInjectionSetIsVisibleToTheGuard is the predicate half of the write-path refusal (pipeline: // checkInjectionTexts). It lives here and not there because the EMBEDDED data carries exactly one target // (ru), and giving it a second, synthetic one to test against would re-mint the unit id of every book // alive: injection.txt is folded byte-for-byte into EmbeddedVersion. So the wiring is pinned end-to-end on // a target with NO rows, and the half-authored shapes are pinned here, on bytes. func TestAPartialInjectionSetIsVisibleToTheGuard(t *testing.T) { required := []string{InjectionKeyGlossaryHeader, InjectionKeyEditorHeader} for _, tc := range []struct { name string rows string missing []string gaps []string }{ { name: "a target with no rows at all is missing both headers", rows: "", missing: []string{InjectionKeyGlossaryHeader, InjectionKeyEditorHeader}, }, { // The half-step the HasData gate cannot see: it reads the glossary header alone, so this target // passes it and hands the editor a law list with an EMPTY line where its instruction belongs. name: "a glossary header without an editor header", rows: "xx\tglossary_header\tГЛОССАРИЙ:\n", missing: []string{InjectionKeyEditorHeader}, }, { name: "both headers and no gender directives is a gender-free target, not a gap", rows: "xx\tglossary_header\tГЛОССАРИЙ:\nxx\teditor_header\tКАНОН:\n", }, { name: "two gender directives out of three is a gap, and it names the missing one", rows: "xx\tglossary_header\tГЛОССАРИЙ:\nxx\teditor_header\tКАНОН:\n" + "xx\tgender_male\t (муж.)\nxx\tgender_female\t (жен.)\n", gaps: []string{InjectionKeyGenderHidden}, }, { // gender_neuter is documented optional: not every target language has a neuter to name. name: "all three directives and no neuter is complete", rows: "xx\tglossary_header\tГЛОССАРИЙ:\nxx\teditor_header\tКАНОН:\n" + "xx\tgender_male\t (муж.)\nxx\tgender_female\t (жен.)\nxx\tgender_hidden\t (скрыт)\n", }, } { t.Run(tc.name, func(t *testing.T) { parsed, err := parseInjection([]byte(tc.rows)) if err != nil { t.Fatal(err) } tx := InjectionTexts{} if p, ok := parsed["xx"]; ok { tx = *p } if got := tx.MissingKeys(required); !equalStrings(got, tc.missing) { t.Errorf("MissingKeys = %v, want %v", got, tc.missing) } if got := tx.GenderKeyGaps(); !equalStrings(got, tc.gaps) { t.Errorf("GenderKeyGaps = %v, want %v", got, tc.gaps) } }) } // ⛔ AND THE SHIPPING TARGET, because every assertion above is about synthetic bytes. The one target // this engine ships must author everything the guard requires AND the whole gender set — otherwise the // refusal and the warning above would fire on the only pair in production. ru := InjectionTextsFor("ru") if m := ru.MissingKeys(required); len(m) > 0 { t.Errorf("the shipping target `ru` is missing injection rows the write path requires: %v", m) } if g := ru.GenderKeyGaps(); len(g) > 0 { t.Errorf("the shipping target `ru` authors a PARTIAL gender set (%v) — the warning would fire on every production run", g) } } func equalStrings(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true }