62 lines
3.1 KiB
Go
62 lines
3.1 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"textmachine/backend/internal/checks"
|
|
"textmachine/backend/internal/config"
|
|
"textmachine/backend/internal/lang"
|
|
"textmachine/backend/internal/llm"
|
|
)
|
|
|
|
// classifyinput_test.go pins the ONE assembler of a live classifyInput.
|
|
//
|
|
// ⚠ THE DEFECT THIS REPLACES WAS A SECOND COPY, NOT A WRONG LINE. classifyOutput and repairReGate each
|
|
// built the input by hand from the same fields. When the target-language screen landed, `TargetScripts` was
|
|
// added to the first and forgotten in the second, so the screen abstained inside the repair re-gate while
|
|
// judging everywhere else — a repair that came back in another language passed a gate whose entire job is
|
|
// «is this still an acceptable completion». It was then fixed at the second site by hand, and a planting
|
|
// proved the fix was guarded by nothing: dropping the field there again left the whole battery green.
|
|
//
|
|
// So the fix is not a test on two call sites, it is one builder with one pin. The assertion below is
|
|
// deliberately written over REFLECTION rather than as a list of fields: a list would have to be updated by
|
|
// the same person who forgets to update the builder, which is the failure it is supposed to prevent. A new
|
|
// axis added to classifyInput and not wired in fails here on the day it is declared.
|
|
func TestClassifyInputForLeavesNoAxisUnset(t *testing.T) {
|
|
ck := checks.CompileCheckersFor(nil, lang.TargetChecksFor("ru"))
|
|
ck.SetSourceScripts(lang.LangScripts("zh"))
|
|
r := &Runner{Book: &config.Book{TargetLang: "ru"}, checkers: ck}
|
|
|
|
// Every argument is chosen NON-ZERO, so that any zero field in the result is an unwired axis and not an
|
|
// artefact of the fixture.
|
|
in := r.classifyInputFor("源文本", "перевод", llm.FinishStop, true)
|
|
|
|
v := reflect.ValueOf(in)
|
|
for i := 0; i < v.NumField(); i++ {
|
|
if v.Field(i).IsZero() {
|
|
t.Errorf("classifyInput.%s came back zero: the builder does not populate it, so EVERY live "+
|
|
"classify() call — the stage gate and the repair re-gate alike — judges without it",
|
|
v.Type().Field(i).Name)
|
|
}
|
|
}
|
|
// The two axes the incident was actually about, asserted by value and not merely as non-zero.
|
|
if in.TargetLang != "ru" {
|
|
t.Errorf("TargetLang = %q, want the book's", in.TargetLang)
|
|
}
|
|
if len(in.TargetScripts) != len(lang.LangScripts("ru")) {
|
|
t.Errorf("TargetScripts must come from the book's target: got %d tables, want %d",
|
|
len(in.TargetScripts), len(lang.LangScripts("ru")))
|
|
}
|
|
if len(in.SourceScripts) != len(lang.LangScripts("zh")) {
|
|
t.Errorf("SourceScripts must come from the run's compiled checkers: got %d tables, want %d",
|
|
len(in.SourceScripts), len(lang.LangScripts("zh")))
|
|
}
|
|
if !in.NonProseReply {
|
|
t.Error("NonProseReply must pass through: a bank role's reply is not prose and must not be echo-screened")
|
|
}
|
|
// And the caller's own flag is not hard-wired: the stage path passes false.
|
|
if r.classifyInputFor("源文本", "перевод", llm.FinishStop, false).NonProseReply {
|
|
t.Error("NonProseReply is pinned to true inside the builder — the prose path would be exempted from the echo rule")
|
|
}
|
|
}
|