textmachine/backend/internal/membank/memneuter_test.go

50 lines
2.9 KiB
Go

package membank
import (
"testing"
"textmachine/backend/internal/store"
)
// memneuter_test.go pins the fix-pack §д money contract of the §3 neuter directive, on the memvoice pin's
// pattern: gender:neuter now RENDERS a directive where the same row rendered nothing before, so it MUST move
// memory_version (a loud, re-pinnable --resnapshot); and the tag respects the mined scope exactly as the
// pack-19 voice fold does, so a mined-only neuter row never re-bills the draft wave that cannot see it.
//
// (The neuter-directive-v1 marker itself is not black-box-isolable from the per-row gender field, which is
// already folded — both move the hash for a neuter row, and no single code version can hold one constant
// while toggling the other. These tests pin the OBSERVABLE contract; the marker exists to carry the RENDER
// revision across the deploy that introduced neuter rendering, which the scope test's line-ordering guards.)
func TestNeuterRowMovesTheBankVersion(t *testing.T) {
base := term("元气", "изначальная ци")
plain := ComputeVersionScopedIn(BankInput{Rows: []store.GlossaryEntry{base}}, false, false)
neut := base
neut.Gender = "neuter"
if withNeuter := ComputeVersionScopedIn(BankInput{Rows: []store.GlossaryEntry{neut}}, false, false); plain == withNeuter {
t.Fatal("a gender:neuter row left the bank version unchanged — it now renders a directive the snapshot cannot see, a silent re-payment")
}
}
// TestNeuterFoldRespectsTheMinedScope mirrors TestVoiceFoldRespectsTheMinedScopeBeforeCountingIt: the tag
// condition is evaluated only AFTER the mined-exclusion filter, so a mined-only neuter row folds into the
// enriched (edit-wave) version and NEVER the base (draft-wave) one — the draft wave never renders a mined
// row's directive, so it must not re-pay for one.
func TestNeuterFoldRespectsTheMinedScope(t *testing.T) {
minedNeuter := term("元气", "изначальная ци")
minedNeuter.Source, minedNeuter.Gender = "mined", "neuter"
minedPlain := minedNeuter
minedPlain.Gender = ""
withNeuter := BankInput{Rows: []store.GlossaryEntry{term("方源", "Фан Юань"), minedNeuter}}
plain := BankInput{Rows: []store.GlossaryEntry{term("方源", "Фан Юань"), minedPlain}}
// BASE: the mined neuter row is out of scope, so neither its gender field nor the tag folds → identical.
if got, want := ComputeVersionScopedIn(withNeuter, false, true), ComputeVersionScopedIn(plain, false, true); got != want {
t.Fatalf("a mined-only neuter row moved the BASE version — the draft wave would re-pay for a directive it never renders\n got %s\n want %s", got, want)
}
// ENRICHED: the mined neuter row IS in scope, so the version moves — a loud re-snapshot for the wire the
// edit wave sees.
if ComputeVersionScopedIn(withNeuter, false, false) == ComputeVersionScopedIn(plain, false, false) {
t.Fatal("a mined neuter row did NOT move the enriched version — the edit wave would miss the directive")
}
}