package main import ( "strings" "testing" "textmachine/backend/internal/pipeline" ) // main_test.go pins the two rules this probe got WRONG before it got them right. Both produced numbers // that looked reasonable, which is the only reason they needed a test: a measurement instrument fails by // reporting, not by crashing. // TestARowTheBankSettledIsInNeitherDenominator. The settled filter runs BEFORE the first paid call, so a // row it removed is neither «a candidate at the role's input» nor «a row the role answered». The first // version of this probe kept it on the pre-call side only: the two denominators then differed by a row // that reached neither, and every pre-call share was quietly diluted. func TestARowTheBankSettledIsInNeitherDenominator(t *testing.T) { s := &source{rows: []pipeline.BankStopRow{ {Src: "方源", Dst: "", SettledByBank: true}, // settled: in neither {Src: "花家", Dst: ""}, // answered with nothing: in the pre-call one only {Src: "青茅山", Dst: "гора Цинмао"}, // an ordinary answered row {Src: "丙等", Dst: "третий разряд"}, // }} pre, answered, settled, unresolved := s.denominators() if pre != 3 || answered != 2 || settled != 1 || unresolved != 1 { t.Errorf("denominators = pre %d, answered %d, settled %d, unresolved %d; want 3, 2, 1, 1", pre, answered, settled, unresolved) } } // TestTheComparedInputNeverCarriesTheAnswer is the other one, and it is the more dangerous: the §4.4 // comparison asks «did the row's INPUT change where the decision changed», so folding the consolidated // rendering into the input makes the answer «the input changed» for every row whose answer changed — 100 % // of the population, by construction, with nothing red anywhere. That is what the first version did (it // read the signature map's note, which names the top proposal's chunk COUNT but not its text, so the // rendering had to be substituted for it). func TestTheComparedInputNeverCarriesTheAnswer(t *testing.T) { before := pipeline.BankStopRow{ Src: "熊家寨", Dst: "селение Сюн", Type: "place", Origin: "banknote", Freq: 4, Conf: 85, Variants: []pipeline.BankStopVariant{{Dst: "селение Сюн", Chunks: 1}}, Contexts: []string{"一段"}, } after := before after.Dst = "крепость рода Сюн" // the ANSWER moved after.Conf = 60 // and so did the role's own confidence after.Invented = true // and the invented mark, which is also an output if rowInput(before, true) != rowInput(after, true) { t.Errorf("a row whose ANSWER moved and whose input did not must compare EQUAL:\n before: %s\n after : %s", rowInput(before, true), rowInput(after, true)) } // And a row whose input really moved must NOT compare equal — otherwise the test above would pass on a // function that returns a constant. moved := before moved.Variants = []pipeline.BankStopVariant{{Dst: "крепость рода Сюн", Chunks: 2}} if rowInput(before, true) == rowInput(moved, true) { t.Error("a row whose draft proposals moved must compare DIFFERENT") } // The contexts are part of the input, and they are what the earlier instrument could not see at all. ctxMoved := before ctxMoved.Contexts = []string{"другой контекст"} if rowInput(before, true) == rowInput(ctxMoved, true) { t.Error("a row whose source contexts moved must compare DIFFERENT when contexts are included") } if rowInput(before, false) != rowInput(ctxMoved, false) { t.Error("and must compare EQUAL when they are not — the two questions are asked separately on purpose") } if !strings.Contains(rowInput(before, false), "drafts=[") { t.Errorf("the compared input must name the drafts it compared: %s", rowInput(before, false)) } }