122 lines
3.8 KiB
Go
122 lines
3.8 KiB
Go
package config
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// terminology_classify_test.go pins the fix-pack §г config branches for the §2 classifier PHASE. The
|
|
// classifier is a phase of the terminology gate, so — like the render phase — an enabled phase must be able to
|
|
// fire (a resolvable non-additive model, a positive budget, its own prompt), and classify_types under a
|
|
// DISABLED gate is a loud error rather than the silent no-op it was (runClassifier is reached only through the
|
|
// enabled gate).
|
|
|
|
// termProject writes a zh-ru pack with a terminologist prompt and, optionally, a classifier prompt.
|
|
func termProject(t *testing.T, withClassifier bool) string {
|
|
t.Helper()
|
|
dir := promptProject(t, "zh-ru")
|
|
body := "sys\n---USER---\n{{text}}"
|
|
writeTmp(t, filepath.Join(dir, "prompts", "zh-ru", "terminologist.md"), body)
|
|
if withClassifier {
|
|
writeTmp(t, filepath.Join(dir, "prompts", "zh-ru", "classifier.md"), body)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
// loadClassify assembles a config with the given terminology-gate body and loads it for zh-ru.
|
|
func loadClassify(t *testing.T, withClassifier bool, gateBody string) error {
|
|
t.Helper()
|
|
dir := termProject(t, withClassifier)
|
|
cfg := draftOnlyConfig(t, dir, gateBody)
|
|
_, err := LoadPipeline(cfg, miniModels(t), "zh-ru", nil)
|
|
return err
|
|
}
|
|
|
|
// ⚠ `mining:` IS PART OF A VALID GATE NOW (backlog row 140). Both bank roles run inside the bank-mining
|
|
// stop, which returns early without a contrast artifact, so an enabled gate with no `contrast_path` is a
|
|
// gate that can never fire — the class this loader refuses everywhere else. The fixture declares it.
|
|
const validClassifyGate = `
|
|
mining:
|
|
contrast_path: contrast.txt
|
|
gates:
|
|
terminology:
|
|
enabled: true
|
|
model: fake
|
|
budget_usd: 1
|
|
target_script: Cyrillic
|
|
classify_types: true
|
|
classify_budget_usd: 0.5
|
|
`
|
|
|
|
func TestClassifyValidConfigLoads(t *testing.T) {
|
|
if err := loadClassify(t, true, validClassifyGate); err != nil {
|
|
t.Fatalf("a well-formed classify config must load: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClassifyMissingPromptFailsLoud(t *testing.T) {
|
|
err := loadClassify(t, false, validClassifyGate) // no classifier.md written
|
|
if err == nil {
|
|
t.Fatal("classify_types on with no classifier.md must fail loud")
|
|
}
|
|
for _, want := range []string{"classify_types", filepath.Join("zh-ru", "classifier.md")} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Errorf("error must name %q, got: %v", want, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClassifyBudgetMustBePositive(t *testing.T) {
|
|
body := `
|
|
gates:
|
|
terminology:
|
|
enabled: true
|
|
model: fake
|
|
budget_usd: 1
|
|
target_script: Cyrillic
|
|
classify_types: true
|
|
classify_budget_usd: 0
|
|
`
|
|
err := loadClassify(t, true, body)
|
|
if err == nil || !strings.Contains(err.Error(), "classify_budget_usd") {
|
|
t.Fatalf("a zero classify budget must fail loud (a phase that can never spend), got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClassifyModelMustExist(t *testing.T) {
|
|
body := `
|
|
gates:
|
|
terminology:
|
|
enabled: true
|
|
model: fake
|
|
budget_usd: 1
|
|
target_script: Cyrillic
|
|
classify_types: true
|
|
classify_budget_usd: 0.5
|
|
classify_model: ghost
|
|
`
|
|
err := loadClassify(t, true, body)
|
|
if err == nil || !strings.Contains(err.Error(), "classify_model") {
|
|
t.Fatalf("an unknown classify_model must fail loud, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// The fix-pack §г hygiene: classify_types under a disabled gate was a silent no-op — now a loud error.
|
|
func TestClassifyTypesRequiresEnabledGate(t *testing.T) {
|
|
body := `
|
|
gates:
|
|
terminology:
|
|
enabled: false
|
|
classify_types: true
|
|
`
|
|
err := loadClassify(t, true, body)
|
|
if err == nil {
|
|
t.Fatal("classify_types with the gate disabled must fail loud, not run as a silent no-op")
|
|
}
|
|
for _, want := range []string{"classify_types", "enabled"} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Errorf("error must name %q, got: %v", want, err)
|
|
}
|
|
}
|
|
}
|