92 lines
3.2 KiB
Go
92 lines
3.2 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 prompt seam (D39 layer 2, pack-15 form):
|
|
// a book whose LangPair() has NO prompt pack FAILS LOUD when the runner opens (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 prompt pack carries ONLY zh-ru — the stages take
|
|
// no prompt path at all, they resolve prompts/<pair>/<role>.md by convention. The book's source_lang
|
|
// is 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", "zh-ru", "translator.md"), "Переводи.\n---USER---\n{{text}}")
|
|
writeFile(t, filepath.Join(dir, "prompts", "zh-ru", "editor.md"), "Редактируй.\n---USER---\n{{text}}\n{{draft}}")
|
|
// The pair layer points the convention at THIS project's prompts dir (repo default: ../../prompts).
|
|
writeFile(t, filepath.Join(dir, "pairs", "zh-ru.yaml"), "pair: zh-ru\nprompts_root: ../prompts\n")
|
|
writeFile(t, filepath.Join(dir, "pairs", "ja-ru.yaml"), "pair: ja-ru\nprompts_root: ../prompts\n")
|
|
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")))
|
|
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
|
|
prompt_version: v-test
|
|
reasoning: "off"
|
|
- name: edit
|
|
role: editor
|
|
model: fake-model
|
|
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 with no ja-ru prompt pack must STOP at load, not silently run zh prompts.
|
|
_, err := NewRunner(setupPairPackProject(t, "ja"), obs.NewLogger())
|
|
if err == nil {
|
|
t.Fatal("a ja book with no ja-ru prompt pack must fail loud (D39 layer 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))
|
|
}
|
|
}
|