108 lines
6.6 KiB
Go
108 lines
6.6 KiB
Go
package terminology
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
"unicode"
|
||
)
|
||
|
||
// decline_test.go: the two shapes a bank role's answer takes when it is NOT a rendering, and the two
|
||
// different places they have to land.
|
||
//
|
||
// ⛔ WHY TWO BUCKETS AND NOT ONE. «Declined» means the role was asked and said it would not render the
|
||
// term — a DECISION (§C2-7's other mode), and the next pack routes it to a second opinion. «Unanswered»
|
||
// means nothing usable came back — silence, truncation, garbage — and that routes to a re-ask of the same
|
||
// model. Folding them together would make the count a lie in both directions and the routing built on it
|
||
// arbitrary. What both must NOT be is a rendering: today a prose decline and «90» are banked and reach the
|
||
// editor as the book's canon, byte-identical to a row the owner signed.
|
||
|
||
// id is the identity normalizer these fixtures use for src keys.
|
||
func idNorm(s string) string { return s }
|
||
|
||
// TestAProseDeclineLandsInTheDeclineBucketAndNotInTheBank pins the parser branch. The REGISTRY of phrases
|
||
// is language data and lives in lang/bankdata (its fold and its shipping emptiness are pinned there); here
|
||
// the predicate is a closure, so this test says what the PARSER does with a decline it is told about, and
|
||
// nothing about which words any language declines in.
|
||
func TestAProseDeclineLandsInTheDeclineBucketAndNotInTheBank(t *testing.T) {
|
||
declines := func(dst string) bool { return strings.ToLower(strings.TrimSpace(dst)) == "не термин" }
|
||
const reply = "家族\tне термин\n方源\tФан Юань"
|
||
|
||
// PREMISE, and it is the defect this branch exists to remove: with NO registry the same answer is
|
||
// BANKED. It passes wellFormedLemma, it is not an echo, it is in the target's own script — and it
|
||
// reaches the editor as law. This assertion is the measured state of the shipping engine (the registry
|
||
// lands EMPTY), not an aspiration, and it is what a first authored phrase changes.
|
||
banked, _, _ := ParseReply(reply, []string{"家族", "方源"}, idNorm, unicode.Scripts["Cyrillic"], nil)
|
||
if banked["家族"] != "не термин" {
|
||
t.Fatalf("premise broken: with no registry the prose decline must still be the (defective) banked rendering, got %q", banked["家族"])
|
||
}
|
||
|
||
got, _, st := ParseReply(reply, []string{"家族", "方源"}, idNorm, unicode.Scripts["Cyrillic"], declines)
|
||
dst, present := got["家族"]
|
||
if !present || dst != "" {
|
||
t.Errorf("a prose decline must land where the sentinel lands — present with an EMPTY rendering (that is what makes it status:auto rather than a term nobody was asked about); got present=%v dst=%q", present, dst)
|
||
}
|
||
if st.DeclinedByPhrase != 1 {
|
||
t.Errorf("DeclinedByPhrase = %d, want 1 — the count is what tells an operator the pair's prompt lost its sentinel format", st.DeclinedByPhrase)
|
||
}
|
||
if st.Bad != 0 {
|
||
t.Errorf("a decline is not a broken line: Bad = %d, want 0 (%+v)", st.Bad, st)
|
||
}
|
||
if got["方源"] != "Фан Юань" {
|
||
t.Errorf("the rendering beside the decline must be untouched, got %q", got["方源"])
|
||
}
|
||
|
||
// ⛔ THE MATCH IS THE WHOLE FIELD, NEVER A SUBSTRING, and this is the arm that costs money if it goes:
|
||
// a false decline drops a rendering the book PAID for out of the bank, and a bank role has no retry to
|
||
// recover it (backlog row 438).
|
||
inside, _, st2 := ParseReply("家族\tклан, а не термин родства", []string{"家族"}, idNorm, unicode.Scripts["Cyrillic"], declines)
|
||
if inside["家族"] == "" {
|
||
t.Errorf("a rendering that merely CONTAINS the phrase must be kept: %+v", st2)
|
||
}
|
||
}
|
||
|
||
// TestARenderingWithNoLettersReadsAsUnansweredNotAsCanon is the other shape: an answer with no letter in
|
||
// any script. «90» is two runes, carries no bracket and no dash, so wellFormedLemma passes it; the
|
||
// answer-language screen is silent on a field with no letters by construction; and past those two it is
|
||
// stamped status:draft and injected as canon.
|
||
func TestARenderingWithNoLettersReadsAsUnansweredNotAsCanon(t *testing.T) {
|
||
// ⚠ THE PUNCTUATION LINE IS TWO RUNES ON PURPOSE. A single «…» is refused one check EARLIER, by
|
||
// wellFormedLemma's minimum length — so a one-rune fixture would go red with this branch DELETED, and
|
||
// the assertion below would be satisfied by somebody else's rule. Two dashes pass every check that
|
||
// precedes this one.
|
||
const reply = "族长\t90\n方源\tОтряд 731\n花家\t——"
|
||
|
||
// PREMISE, and it has to be stated as a CONTROL rather than as «what the parser used to do» — the check
|
||
// under test is unconditional, so the old behaviour is no longer callable. The control differs from the
|
||
// refused line in ONE rune: «Глава 90» carries a letter. If this fixture were failing for some other
|
||
// reason — a mis-split column, a normalizer, the answer-language screen — the control would be refused
|
||
// too, and every assertion below would be about a broken fixture instead of about the rule.
|
||
ctrl, _, cst := ParseReply("族长\tГлава 90", []string{"族长"}, idNorm, unicode.Scripts["Cyrillic"], nil)
|
||
if ctrl["族长"] != "Глава 90" {
|
||
t.Fatalf("premise broken: the same line with ONE letter added must be accepted, got %q (%+v)", ctrl["族长"], cst)
|
||
}
|
||
|
||
got, _, st := ParseReply(reply, []string{"族长", "方源", "花家"}, idNorm, unicode.Scripts["Cyrillic"], nil)
|
||
if _, present := got["族长"]; present {
|
||
t.Errorf("«90» must be REFUSED, and refused means ABSENT from the map: a key present with an empty rendering is the DECLINE bucket, and the role did not decline — it answered with garbage (%+v)", st)
|
||
}
|
||
if _, present := got["花家"]; present {
|
||
t.Errorf("a rendering of pure punctuation must be refused too (%+v)", st)
|
||
}
|
||
if st.NoLetters != 2 || st.Bad != 2 {
|
||
t.Errorf("NoLetters = %d, Bad = %d, want 2 and 2 (%+v)", st.NoLetters, st.Bad, st)
|
||
}
|
||
if len(st.NoLettersSamples) != 2 || !strings.Contains(strings.Join(st.NoLettersSamples, "; "), "族长 → 90") {
|
||
t.Errorf("the refused lines must be NAMED, not only counted: %v", st.NoLettersSamples)
|
||
}
|
||
// ⛔ THE CONTROL, and it is the direction of error this check declares: a DIGIT is legitimate inside a
|
||
// rendering. The test is the absence of letters, not the presence of numbers — refusing «Отряд 731»
|
||
// would cost the bank real rows.
|
||
if got["方源"] != "Отряд 731" {
|
||
t.Errorf("a rendering with letters AND digits must be kept, got %q", got["方源"])
|
||
}
|
||
// And the off-language screen keeps its own verdict: a letterless field is not «answered in another
|
||
// script», and merging the two counters would make one number answer two questions.
|
||
if st.OffLanguage != 0 {
|
||
t.Errorf("OffLanguage = %d, want 0 — a field with no letters is not a field in the wrong script (%+v)", st.OffLanguage, st)
|
||
}
|
||
}
|