textmachine/backend/internal/pipeline/bankfold_test.go

101 lines
4.4 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 pipeline
import (
"testing"
"unicode"
"textmachine/backend/internal/store"
"textmachine/backend/internal/terminology"
)
// stateWithProposals builds one per-chunk telemetry row carrying the given banknote lines.
func stateWithProposals(chapter, chunk int, lines ...bankEntry) store.RetrievalState {
return store.RetrievalState{Chapter: chapter, ChunkIdx: chunk, BanknoteDetail: bankProposalsJSON(lines)}
}
// TestBankFoldJoinsEnclosedAndBareSurfaces pins the measured defect: one draft wave proposed the same
// title twice, once wrapped in paired marks and once bare, with two different renderings. Unfolded that is
// two rows of the owner's signature map, and neither joins the miner's candidate space.
func TestBankFoldJoinsEnclosedAndBareSurfaces(t *testing.T) {
obs := bankObservedByKey([]store.RetrievalState{
stateWithProposals(1, 0, bankEntry{Src: "《咏梅》", Dst: "Ода сливе", Type: "title"}),
stateWithProposals(1, 1, bankEntry{Src: "咏梅", Dst: "Воспевая сливу", Type: "title"}),
}, nil)
if len(obs) != 1 {
t.Fatalf("the two surfaces must fold into ONE candidate, got %d: %+v", len(obs), obs)
}
if obs[0].Key != "咏梅" || obs[0].Src != "咏梅" {
t.Fatalf("key and displayed surface must both be the bare form, got key=%q src=%q", obs[0].Key, obs[0].Src)
}
if len(obs[0].Proposals) != 2 {
t.Fatalf("both renderings must survive as alternatives for the owner, got %+v", obs[0].Proposals)
}
}
// TestBankFoldScreensTheAnswerLanguage pins the draft side of the answer-language screen: a rendering in
// another script never becomes evidence, and the same rows pass untouched when no script is declared.
func TestBankFoldScreensTheAnswerLanguage(t *testing.T) {
states := []store.RetrievalState{stateWithProposals(1, 0,
bankEntry{Src: "元海", Dst: "Primordial Sea", Type: "term"},
bankEntry{Src: "方源", Dst: "Фан Юань", Type: "name"},
)}
f := newBankFold(unicode.Scripts["Cyrillic"])
addRetrievalStates(f, states)
obs := f.observed()
if len(obs) != 1 || obs[0].Key != "方源" {
t.Fatalf("only the target-language proposal may become evidence, got %+v", obs)
}
if f.OffLanguage != 1 {
t.Fatalf("the dropped proposal must be counted, got %d", f.OffLanguage)
}
if inert := bankObservedByKey(states, nil); len(inert) != 2 {
t.Fatalf("with no declared script the fold must be inert, got %+v", inert)
}
}
// TestBankFoldCountsChunksNotRows is the aggregation invariant in miniature: the same chunk seen twice
// (a re-drafted chunk keeps both checkpoints) votes ONCE, so a union never inflates the evidence a
// consolidation is ranked on, while a genuinely different chunk does add its vote.
func TestBankFoldCountsChunksNotRows(t *testing.T) {
prop := []bankProposal{{SrcKey: "方源", Src: "方源", Dst: "Фан Юань", Type: "name"}}
f := newBankFold(nil)
f.add(1, 0, prop)
f.add(1, 0, prop) // the same chunk, drafted a second time
if got := f.observed()[0].Proposals[0].Chunks; got != 1 {
t.Fatalf("one chunk must count once however many stored answers mention it, got %d", got)
}
f.add(1, 1, prop)
if got := f.observed()[0].Proposals[0].Chunks; got != 2 {
t.Fatalf("a second chunk must add its own vote, got %d", got)
}
}
// TestBankFoldIsDeterministic pins that neither the key order nor the rendering order depends on map
// iteration — the signature map must be byte-identical between two runs over the same evidence.
func TestBankFoldIsDeterministic(t *testing.T) {
states := []store.RetrievalState{
stateWithProposals(1, 0, bankEntry{Src: "花家", Dst: "Дом Хуа", Type: "name"}),
stateWithProposals(1, 1, bankEntry{Src: "方源", Dst: "Фан Юань", Type: "name"}),
stateWithProposals(1, 2, bankEntry{Src: "方源", Dst: "Фан Юань", Type: "name"}),
stateWithProposals(1, 3, bankEntry{Src: "方源", Dst: "Фан-Юань", Type: "name"}),
}
var first []terminology.Observed
for i := 0; i < 8; i++ {
got := bankObservedByKey(states, nil)
if i == 0 {
first = got
continue
}
if len(got) != len(first) {
t.Fatalf("unstable fold size: %d vs %d", len(got), len(first))
}
for j := range got {
if got[j].Key != first[j].Key || got[j].Proposals[0].Dst != first[j].Proposals[0].Dst {
t.Fatalf("unstable order at %d: %+v vs %+v", j, got[j], first[j])
}
}
}
if first[0].Key != "方源" || first[0].Proposals[0].Dst != "Фан Юань" {
t.Fatalf("keys sort, renderings rank most-proposed-first: %+v", first)
}
}