package config import ( "os" "path/filepath" "strings" "testing" ) // book_refusal_test.go: the ONE distinction PD-196 rests on — "the text we were handed is unusable" // against "the operator's config is broken". They have opposite repairs, and upstream the first one // authorises deleting a user's upload while the second must not. func bookFixture(t *testing.T, sourceName string) string { t.Helper() dir := t.TempDir() if sourceName != "" { if err := os.WriteFile(filepath.Join(dir, sourceName), []byte("текст"), 0o644); err != nil { t.Fatal(err) } } path := filepath.Join(dir, "book.yaml") body := ` book_id: b title: Т source_lang: ja target_lang: ru genre: g audience: a venuti: 0.5 honorifics: keep transcription: polivanov footnotes: minimal pipeline: pipeline.yaml models: models.yaml source_file: source.txt ceilings: { book_usd: 1.0 } ` if err := os.WriteFile(path, []byte(body), 0o644); err != nil { t.Fatal(err) } return path } func TestAMissingSourceIsAnOrdinaryConfigProblem(t *testing.T) { // It is deliberately NOT a verdict about the user's text. A path that is not there says something // about the configuration or the host — a template naming a filename the upload route did not use, a // mount that vanished — and the class that blames the text is the one an automated intake acts on by // DELETING the upload (PD-196, pipeline/refusal.go). So this stays an ordinary problem, and the // caller maps it to the config exit code. path := bookFixture(t, "") // the source_file the config names is not there err := loadErr(t, path) if !strings.Contains(err.Error(), "source_file") || !strings.Contains(err.Error(), "not readable") { t.Fatalf("the operator must be told which setting is wrong: %v", err) } } func TestEveryFaultIsReportedTogether(t *testing.T) { // The aggregate is the contract: an operator fixing a hand-written config wants every complaint at // once, not one per run. path := bookFixture(t, "") body, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(strings.Replace(string(body), "book_id: b", "book_id: \"\"", 1)), 0o644); err != nil { t.Fatal(err) } msg := loadErr(t, path).Error() if !strings.Contains(msg, "book_id") || !strings.Contains(msg, "source_file") { t.Fatalf("want both faults named, got %v", msg) } } func loadErr(t *testing.T, path string) error { t.Helper() _, err := LoadBook(path) if err == nil { t.Fatal("this book must not load") } return err } func TestTheBookRemembersTheDirectoryItWasLoadedFrom(t *testing.T) { // The run-event journal lives in the BOOK's directory (D39.106 §2) — the directory of the config the // caller passed, which is not necessarily the project database's. path := bookFixture(t, "source.txt") b, err := LoadBook(path) if err != nil { t.Fatal(err) } if b.Dir != filepath.Dir(path) { t.Fatalf("Dir = %q, want %q", b.Dir, filepath.Dir(path)) } }