textmachine/backend/internal/store/bankbasis_test.go

76 lines
3.8 KiB
Go

package store
import "testing"
// TestBasisStoreRefusesAnEmptyRendering pins the guarantee at the WRITE, which is where it has to live.
//
// ⛔ WHY AT THE WRITE AND NOT ONLY AT THE CALLER. The basis exists to hand an answer back. An empty
// rendering handed back is the silent regression the whole mechanism is built against: the emission drops
// to the raw first-chunk guess at status `auto`, the run looks cheaper and the bank gets poorer with
// nothing red. The pipeline also skips such rows before it calls this — belt AND braces — but a guarantee
// kept only by a caller is one the next caller can forget, and this is the money path.
func TestBasisStoreRefusesAnEmptyRendering(t *testing.T) {
s, _ := openTemp(t)
good := BankBasisRow{SrcKey: "元石", FPVersion: "v1", FPWhole: "abc", FPCanon: "def", Dst: "юаньши", Type: "name"}
bad := []BankBasisRow{
{SrcKey: "空", FPVersion: "v1", FPWhole: "abc", Dst: ""}, // no rendering: nothing to serve
{SrcKey: "", FPVersion: "v1", FPWhole: "abc", Dst: "что-то"}, // no key: it would answer for nobody
{SrcKey: "无", FPVersion: "v1", FPWhole: "", Dst: "что-то"}, // no fingerprint: nothing to compare
{SrcKey: "版", FPVersion: "", FPWhole: "abc", Dst: "что-то"}, // no shape: cannot say which rule decided it
}
if err := s.PutBankBasis("b", append([]BankBasisRow{good}, bad...)); err != nil {
t.Fatal(err)
}
got, err := s.BankBasisForBook("b")
if err != nil {
t.Fatal(err)
}
// The control first: the GOOD row landed, so «nothing was written» cannot masquerade as «the bad rows
// were refused».
if _, ok := got["元石"]; !ok {
t.Fatalf("the well-formed row did not land at all (%d row(s) stored): this test would then pass on a store that writes nothing", len(got))
}
for _, r := range bad {
if _, landed := got[r.SrcKey]; landed && r.SrcKey != "" {
t.Errorf("a row with src=%q dst=%q fp=%q shape=%q was STORED: a later purchase would serve it back, and an empty or unattributable answer served back is the loss this mechanism exists to prevent",
r.SrcKey, r.Dst, r.FPWhole, r.FPVersion)
}
}
if len(got) != 1 {
t.Errorf("stored %d row(s), want exactly the one well-formed row: %+v", len(got), got)
}
}
// TestBasisStoreReplacesRatherThanAccumulates pins the guarantee this table does NOT inherit from its
// neighbour. bank_stop_presented only grows and has no delete on purpose; a decision re-taken must
// OVERWRITE, or the predicate keeps comparing against evidence the book has moved past and serves an answer
// that was right two purchases ago.
func TestBasisStoreReplacesRatherThanAccumulates(t *testing.T) {
s, _ := openTemp(t)
first := BankBasisRow{SrcKey: "元石", FPVersion: "v1", FPWhole: "old", FPCanon: "oldc", Dst: "юаньши", Type: "name", Gender: "none"}
if err := s.PutBankBasis("b", []BankBasisRow{first}); err != nil {
t.Fatal(err)
}
second := BankBasisRow{SrcKey: "元石", FPVersion: "v2", FPWhole: "new", FPCanon: "newc", Dst: "камень юань", Type: "term", Gender: "male"}
if err := s.PutBankBasis("b", []BankBasisRow{second}); err != nil {
t.Fatal(err)
}
got, err := s.BankBasisForBook("b")
if err != nil {
t.Fatal(err)
}
if len(got) != 1 {
t.Fatalf("the same surface produced %d rows: the table accumulates where it must replace, and the predicate would meet two answers for one term", len(got))
}
if got["元石"] != second {
t.Fatalf("the row was not replaced: got %+v, want %+v", got["元石"], second)
}
// A different BOOK must not be touched by either write: the key is (book, surface), and a basis that
// leaked across books would answer one book's terms with another's canon.
if err := s.PutBankBasis("other", []BankBasisRow{first}); err != nil {
t.Fatal(err)
}
if again, _ := s.BankBasisForBook("b"); again["元石"] != second {
t.Fatalf("writing another book's basis changed this one: %+v", again["元石"])
}
}