99 lines
4.5 KiB
Go
99 lines
4.5 KiB
Go
package pipeline
|
||
|
||
import (
|
||
"testing"
|
||
|
||
"textmachine/backend/internal/store"
|
||
)
|
||
|
||
// memory_f1_test.go covers the F1 content-hash (D8): memoryVersion is a pure function of
|
||
// the frozen APPROVED rows + the algorithm versions. A change to the approved glossary
|
||
// re-pins loudly; auto/draft churn does not; the row id never leaks in.
|
||
|
||
func TestMemoryVersionStableAndSensitive(t *testing.T) {
|
||
base := []store.GlossaryEntry{
|
||
gl("阿Q", "А-кью", "name", "approved"),
|
||
gl("鲁镇", "Лучжэнь", "", "approved"),
|
||
}
|
||
v0 := computeMemoryVersion(base, false)
|
||
|
||
// Identical rows → identical version (idempotent; drift-proof content hash).
|
||
if computeMemoryVersion(base, false) != v0 {
|
||
t.Fatal("memoryVersion is not stable for identical rows")
|
||
}
|
||
|
||
// Changing an APPROVED dst → different version (a loud --resnapshot).
|
||
changed := []store.GlossaryEntry{
|
||
gl("阿Q", "А-Кью (изменено)", "name", "approved"),
|
||
gl("鲁镇", "Лучжэнь", "", "approved"),
|
||
}
|
||
if computeMemoryVersion(changed, false) == v0 {
|
||
t.Error("changing an approved dst did not change memoryVersion — F1 would silently regress")
|
||
}
|
||
|
||
// Adding an alias to an approved row → different version (aliases affect matching).
|
||
withAlias := []store.GlossaryEntry{
|
||
func() store.GlossaryEntry { e := gl("阿Q", "А-кью", "name", "approved"); e.Aliases = []store.GlossaryAlias{alias("阿桂")}; return e }(),
|
||
gl("鲁镇", "Лучжэнь", "", "approved"),
|
||
}
|
||
if computeMemoryVersion(withAlias, false) == v0 {
|
||
t.Error("adding an alias to an approved row did not change memoryVersion")
|
||
}
|
||
|
||
// Changing an AUTO row → SAME version (approved-only, D8; auto changes are caught by
|
||
// the per-chunk content_hash instead).
|
||
withAuto := append(append([]store.GlossaryEntry{}, base...), gl("小D", "Малыш Дэ", "", "auto"))
|
||
if computeMemoryVersion(withAuto, false) != v0 {
|
||
t.Error("an auto row changed memoryVersion — it must hash only approved rows (D8)")
|
||
}
|
||
withAuto2 := append(append([]store.GlossaryEntry{}, base...), gl("小D", "Другой Дэ", "", "auto"))
|
||
if computeMemoryVersion(withAuto, false) != computeMemoryVersion(withAuto2, false) {
|
||
t.Error("editing an auto row changed memoryVersion — auto must not enter the F1 hash")
|
||
}
|
||
}
|
||
|
||
// TestMemoryVersionIgnoresID: the autoincrement glossary.id is fresh on every replace;
|
||
// it must NOT feed the hash (else a re-seed of identical content would false-resnapshot).
|
||
func TestMemoryVersionIgnoresID(t *testing.T) {
|
||
a := []store.GlossaryEntry{{BookID: "b", ID: 1, Src: "甲", Dst: "А", Status: "approved"}}
|
||
b := []store.GlossaryEntry{{BookID: "b", ID: 999, Src: "甲", Dst: "А", Status: "approved"}}
|
||
if computeMemoryVersion(a, false) != computeMemoryVersion(b, false) {
|
||
t.Error("memoryVersion depends on glossary.id — a re-seed would false-resnapshot")
|
||
}
|
||
}
|
||
|
||
// TestMaterializeMemoryVersionMatches: the bank's Version() equals computeMemoryVersion
|
||
// of its input rows (the runner hashes the same thing it matches on).
|
||
func TestMaterializeMemoryVersionMatches(t *testing.T) {
|
||
rows := specGlossary()
|
||
b := materializeMemory(rows, false)
|
||
if b.Version() != computeMemoryVersion(rows, false) {
|
||
t.Error("bank Version() diverges from computeMemoryVersion of its rows")
|
||
}
|
||
}
|
||
|
||
// TestMemoryVersionGateFoldsUnapprovedDecl covers self-review #4: when the post-check
|
||
// hard gate is ON, editing a DRAFT/auto row's decl (which changes the gate's resolved
|
||
// disposition but is in NEITHER content_hash NOR the approved-only hash) MUST change
|
||
// memoryVersion — else a $0 resume silently flips a chunk. Gate OFF: it must NOT change
|
||
// it (D8 approved-only holds; decl affects only the recomputed retrieval-state).
|
||
func TestMemoryVersionGateFoldsUnapprovedDecl(t *testing.T) {
|
||
mk := func(declForm string) []store.GlossaryEntry {
|
||
e := gl("鈴木", "Судзуки", "", "draft")
|
||
e.Decl = declJSON(false, declForm)
|
||
return []store.GlossaryEntry{gl("阿Q", "А-кью", "", "approved"), e}
|
||
}
|
||
a := mk("Судзуки")
|
||
b := mk("Судзуку") // only the DRAFT decl form changed (dst unchanged, status draft)
|
||
|
||
if computeMemoryVersion(a, false) != computeMemoryVersion(b, false) {
|
||
t.Error("gate OFF: a draft decl change must NOT move memoryVersion (D8 approved-only)")
|
||
}
|
||
if computeMemoryVersion(a, true) == computeMemoryVersion(b, true) {
|
||
t.Error("gate ON: a draft decl change MUST move memoryVersion (else a resume silently flips the chunk)")
|
||
}
|
||
// Toggling the gate itself also changes the version (a mode change is a loud re-pin).
|
||
if computeMemoryVersion(a, false) == computeMemoryVersion(a, true) {
|
||
t.Error("toggling the post-check gate must change memoryVersion")
|
||
}
|
||
}
|