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) } }