textmachine/backend/internal/lang/embedded_test.go

156 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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-<hash>", 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
}