textmachine/backend/internal/pipeline/cjktarget_screen_test.go

80 lines
4.5 KiB
Go

package pipeline
import (
"strings"
"testing"
"textmachine/backend/internal/lang"
"textmachine/backend/internal/langscreen"
"textmachine/backend/internal/llm"
)
// cjktarget_screen_test.go pins that the target-language screen runs for a CJK TARGET.
//
// ⚠ IT DID NOT, AND THE DOC COMMENT SAID IT DID. The echo rule and the screen sat under one gate,
// `!isCJKTarget(target)`, so →ja / →zh / →ko books were screened by nothing at all — while langscreen's own
// header promised «a →ja one is screened against everything that is not han/kana». The two rules ask
// opposite questions and only ONE of them is meaningless for a CJK target: «is the output in the SOURCE's
// script» cannot be asked of a →ja rendering of a zh book (a correct one is full of han), but «is the output
// in the TARGET's script» is perfectly answerable and an English completion fails it.
//
// This is a goal-2 test, not a nicety: the standing review question is «does a pair the repo has never seen
// work without editing Go», and for every CJK target the answer was no.
func TestACJKTargetIsScreenedToo(t *testing.T) {
english := strings.Repeat("The librarian walked the long corridor and remembered the lecture. ", 8)
if n := countLetters(english); n < langscreen.MinLetters {
t.Fatalf("premise broken: fixture must clear the evidence floor, got %d letters", n)
}
in := func(out, target string) classifyInput {
return classifyInput{Output: out, Finish: llm.FinishStop, TargetLang: target,
SourceScripts: lang.LangScripts("zh"), TargetScripts: lang.LangScripts(target)}
}
for _, target := range []string{"ja", "zh", "ko"} {
if !lang.IsCJKScriptLang(target) {
t.Fatalf("premise broken: %s must be a CJK-script target for this test to mean anything", target)
}
got := classify(in(english, target))
if got.Reason != FlagOffTargetLang {
t.Errorf("→%s: an English completion must flag off-target, got %q (%s)", target, got.Reason, got.Detail)
}
}
// A HEALTHY completion in the target must stay clean — otherwise the fix above would have bought the
// screen by making every CJK book unshippable.
japanese := strings.Repeat("彼は長い廊下を歩きながら、講義のことを思い出していた。", 14)
if n := countLetters(japanese); n < langscreen.MinLetters {
t.Fatalf("premise broken: the healthy fixture must clear the floor too, got %d letters", n)
}
if got := classify(in(japanese, "ja")); got.Reason != reasonOK {
t.Errorf("a healthy Japanese completion for a →ja book must pass, got %q (%s)", got.Reason, got.Detail)
}
// ⚠ AND THE DOCUMENTED MISS, asserted as a miss. ja declares han, so a purely CHINESE completion for a
// →ja book scores 1.000 and is NOT flagged. That is the superset limit langscreen's header names, it is
// a property of the question and not a defect of this wiring, and it is pinned here so that a future
// reader cannot mistake this screen for protection against a zh echo on a →ja book.
chinese := strings.Repeat("方源看着蛊,那是一只月光蛊。", 30)
if got := classify(in(chinese, "ja")); got.Reason == FlagOffTargetLang {
t.Error("the superset limit changed: a Chinese completion for →ja is now flagged off-target. That may " +
"be an improvement, but langscreen's header documents the miss and this test records it — update both together")
}
}
// TestTheEchoRuleStaysOffForACJKTarget is the other half, and it is what stops the fix above from being an
// over-correction: lifting the screen out of the gate must NOT lift the echo rule with it, or every correct
// →ja chapter would be flagged as an untranslated echo of its Chinese source.
func TestTheEchoRuleStaysOffForACJKTarget(t *testing.T) {
chinese := strings.Repeat("方源看着蛊,那是一只月光蛊。", 30)
got := classify(classifyInput{Output: chinese, Finish: llm.FinishStop, TargetLang: "ja",
SourceScripts: lang.LangScripts("zh"), TargetScripts: lang.LangScripts("ja")})
if got.Reason == FlagCJKArtifact {
t.Fatal("the echo rule fired for a CJK TARGET: «the output is in the source's script» is not a " +
"question that can be asked of a →ja book, and asking it flags every correct chapter")
}
// A →ru book keeps the echo rule — the guard against loosening the gate for everyone.
if r := classify(classifyInput{Output: chinese, Finish: llm.FinishStop, TargetLang: "ru",
SourceScripts: lang.LangScripts("zh"), TargetScripts: lang.LangScripts("ru")}); r.Reason != FlagCJKArtifact {
t.Fatalf("a →ru book must still name a source echo an echo, got %q", r.Reason)
}
}