91 lines
3 KiB
Go
91 lines
3 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"textmachine/backend/internal/obs"
|
|
)
|
|
|
|
// runner_pairpack_test.go is the end-to-end proof of the pair-keyed prompt seam (D39 слой 2): a book
|
|
// whose LangPair() is NOT in a stage's pair pack FAILS LOUD at loadTemplates (closing the latent bug
|
|
// where a ja book rode the zh-parataxis prompt), while a book of the populated pair resolves cleanly.
|
|
|
|
// setupPairPackProject writes a minimal project whose stages use a pair-keyed `prompts` pack carrying
|
|
// ONLY zh-ru, with the book's source_lang set to srcLang. Returns the book.yaml path.
|
|
func setupPairPackProject(t *testing.T, srcLang string) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "prompts", "translator.md"), "Переводи.\n---USER---\n{{text}}")
|
|
writeFile(t, filepath.Join(dir, "prompts", "editor.md"), "Редактируй.\n---USER---\n{{text}}\n{{draft}}")
|
|
writeFile(t, filepath.Join(dir, "models.yaml"), fmt.Sprintf(`
|
|
prices_checked: %q
|
|
default_model: fake-model
|
|
providers:
|
|
fake: { kind: openai, base_url: http://127.0.0.1:0 }
|
|
models:
|
|
fake-model: { provider: fake, price: { input_per_m: 1.0, output_per_m: 2.0 } }
|
|
`, time.Now().UTC().Format("2006-01-02")))
|
|
// Pair-keyed pack — ONLY zh-ru is populated.
|
|
writeFile(t, filepath.Join(dir, "pipeline.yaml"), `
|
|
core: C1
|
|
version: 1
|
|
defaults: { max_output_ratio: 2.0, min_max_tokens: 512 }
|
|
stages:
|
|
- name: draft
|
|
role: translator
|
|
model: fake-model
|
|
prompts: { zh-ru: prompts/translator.md }
|
|
prompt_version: v-test
|
|
reasoning: "off"
|
|
- name: edit
|
|
role: editor
|
|
model: fake-model
|
|
prompts: { zh-ru: prompts/editor.md }
|
|
prompt_version: v-test
|
|
reasoning: "off"
|
|
`)
|
|
writeFile(t, filepath.Join(dir, "source.txt"), "四代族长 вошёл в зал.")
|
|
writeFile(t, filepath.Join(dir, "book.yaml"), fmt.Sprintf(`
|
|
book_id: pairpack
|
|
title: Тест
|
|
source_lang: %s
|
|
target_lang: ru
|
|
genre: ранобэ
|
|
audience: тест
|
|
venuti: 0.5
|
|
honorifics: keep
|
|
transcription: polivanov
|
|
pipeline: pipeline.yaml
|
|
models: models.yaml
|
|
source_file: source.txt
|
|
ceilings: { book_usd: 1.0, day_usd: 2.0 }
|
|
`, srcLang))
|
|
return filepath.Join(dir, "book.yaml")
|
|
}
|
|
|
|
func TestPairPackJaBookFailsLoud(t *testing.T) {
|
|
// A ja→ru book against a zh-ru-only pack must STOP at loadTemplates, not silently run zh prompts.
|
|
_, err := NewRunner(setupPairPackProject(t, "ja"), obs.NewLogger())
|
|
if err == nil {
|
|
t.Fatal("a ja book against a zh-ru-only prompt pack must fail loud (D39 слой 2)")
|
|
}
|
|
if !strings.Contains(err.Error(), "ja-ru") {
|
|
t.Errorf("fail-loud error must name the missing pair ja-ru, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPairPackZhBookResolves(t *testing.T) {
|
|
// A zh→ru book of the populated pair resolves the pack cleanly (prod path, behaviour-neutral).
|
|
r, err := NewRunner(setupPairPackProject(t, "zh"), obs.NewLogger())
|
|
if err != nil {
|
|
t.Fatalf("a zh book must resolve the zh-ru pack: %v", err)
|
|
}
|
|
defer r.Close()
|
|
if len(r.templates) != 2 {
|
|
t.Errorf("expected both stage templates loaded, got %d", len(r.templates))
|
|
}
|
|
}
|