package pipeline import ( "os" "path/filepath" "strings" "testing" "textmachine/backend/internal/lang" ) // readerholewords_test.go: what the READER'S FILE says at a hole — the one surface the product exists for. // // ⛔ THE DEFECT THIS PINS WAS FOUND IN THE WORDS, NOT IN A TEST. The writer chose the sentence by the hole's // KIND alone, and `withheld` says «этот фрагмент … требует проверки человеком». That is true of a refusal // and of a contaminated output. It is FALSE of a stop MARK — a call a person cut, or a re-attack a ceiling // refused — because nothing is wrong with that fragment: it was paid for, not finished, and the next run // does it. A reader was being sent to wait for a human who cannot help, and the money mark of backlog row // 291 was about to join the same sentence (before it, such a unit had no row at all and the file said the // true thing: «ещё не переведён»). // readerWordsFor writes a minimal target-language word set and loads it, returning distinguishable // sentences so a test can say WHICH one the writer chose. func readerWordsFor(t *testing.T) lang.ReaderWords { t.Helper() root := t.TempDir() if err := os.MkdirAll(filepath.Join(root, "ru"), 0o755); err != nil { t.Fatal(err) } writeFile(t, filepath.Join(root, "ru", "reader.txt"), "hole.pending\tНЕ ПЕРЕВЕДЕНО.\nhole.withheld\tТРЕБУЕТ ПРОВЕРКИ ЧЕЛОВЕКОМ.\nhole.incomplete\tНедостаёт {dropped}.\n"+ "hole.stale\tУстарело.\nhole.ghost\tВне нарезки {ghost}.\nnotice.holes\tПропусков {holes} из {total}.\nnotice.ghost\tВне нарезки всего {ghost}.\n") words, present, err := lang.LoadReaderWords(root, "ru") if err != nil || !present { t.Fatalf("reader words: present=%v err=%v", present, err) } return words } // TestTheReadersFileDoesNotSendThemToWaitForAHumanWhoCannotHelp is the pin, and it is a table over the two // CLASSES rather than over one reason: a mark the next run erases, and a verdict a person must look at. func TestTheReadersFileDoesNotSendThemToWaitForAHumanWhoCannotHelp(t *testing.T) { words := readerWordsFor(t) for _, tc := range []struct { name string reason FlagReason dropped int want string }{ {name: "a re-attack no money was left for", reason: FlagRetryUnaffordable, want: "НЕ ПЕРЕВЕДЕНО."}, {name: "a call a person cut", reason: FlagCancelled, want: "НЕ ПЕРЕВЕДЕНО."}, // A stop mark over a unit that ALSO lost a member for good: no purchase brings that text back, so // the cheerier sentence would promise what the next run cannot deliver. {name: "a stop mark over a unit that lost a member for good", reason: FlagRetryUnaffordable, dropped: 1, want: "ТРЕБУЕТ ПРОВЕРКИ ЧЕЛОВЕКОМ."}, // The control, and without it the assertion above would pass on a writer that said «not translated» // about EVERYTHING: a verdict a human really does have to look at keeps the withheld sentence. {name: "a provider refusal", reason: FlagHardRefusal, want: "ТРЕБУЕТ ПРОВЕРКИ ЧЕЛОВЕКОМ."}, {name: "a contaminated output that was dropped", reason: FlagSanitizerDefect, want: "ТРЕБУЕТ ПРОВЕРКИ ЧЕЛОВЕКОМ."}, } { t.Run(tc.name, func(t *testing.T) { exp := &BookExport{ BookID: "syn", TotalUnits: 1, Chunks: []ChunkExport{{Chapter: 1, ChunkIdx: 0, Disposition: string(DispFlagged), FlagReason: string(tc.reason), FinalText: "", DroppedMembers: tc.dropped, DroppedReason: string(FlagHardRefusal)}}, } // The premise: this record IS the hole kind whose sentence is under test. A projection that // classified differently would make the assertion below about another branch entirely. if got := UnitHole(exp.Chunks[0]); got != HoleWithheld { t.Fatalf("the fixture's unit is hole %q, not %q — this test is about the withheld branch", got, HoleWithheld) } book, holes, err := assembleBook(exp, nil, "книга", "ru", words) if err != nil { t.Fatal(err) } if len(book.Chapters) != 1 || len(book.Chapters[0].Paragraphs) != 1 { t.Fatalf("the file is not one chapter with one marked hole: %+v", book.Chapters) } got := book.Chapters[0].Paragraphs[0] t.Logf("reason %q ⇒ the reader reads %q", tc.reason, got) if !strings.Contains(got, tc.want) { t.Fatalf("the reader reads %q for reason %q, want %q: a stop mark is work the next run buys, and telling a reader it needs a human is a sentence they can act on wrongly", got, tc.reason, tc.want) } // ⛔ AND THE REASON SURVIVES ON THE HOLE. It is what makes a third sentence of its own («paid // for, not finished: top up and resume») a row in reader.txt plus a branch, rather than a // re-design: an operator's refusal text and any later phrase both read it from here. if len(holes) != 1 || holes[0].Reason != string(tc.reason) { t.Fatalf("the hole lost its reason: %+v", holes) } }) } } // TestTheOperatorsRefusalTellsHimBothHalves is the THIRD surface of the same distinction, and the one the // first two repairs skipped: the build's refusal text is what the person who can ACT reads, and it was // telling him only the cheerful half. A unit can be «paid for and not done» AND short a member for good; // told only the first, he tops up, resumes, and the build refuses again with a different hole. // // ⛔ The reason the other two surfaces got the caveat and this one could not: the hole record it reads did // not carry the drop count for a withheld unit at all. The field existed for `incomplete` and was simply // not filled — «took the form, not the guarantee», one surface further along. func TestTheOperatorsRefusalTellsHimBothHalves(t *testing.T) { exp := &BookExport{BookID: "syn", TotalUnits: 3} holes := []hole{ {Kind: HoleWithheld, Chapter: 1, Unit: 0, Reason: string(FlagRetryUnaffordable)}, {Kind: HoleWithheld, Chapter: 2, Unit: 0, Reason: string(FlagRetryUnaffordable), Dropped: 1}, // The control: a real verdict keeps the plain sentence, so «both halves» above is a statement about // this shape and not what the text says about everything. {Kind: HoleWithheld, Chapter: 3, Unit: 0, Reason: string(FlagHardRefusal)}, } err := describeHoles("syn", exp, holes, false) if err == nil { t.Fatal("a book with holes and no --partial must refuse") } printed := err.Error() t.Logf("what the operator reads:\n%s", printed) for _, want := range []string{ "chapter 1 unit 0: withheld — paid for and NOT done; the next run re-does it (retry_unaffordable)", "chapter 2 unit 0: withheld — paid for and NOT done, AND 1 member(s) of it are missing for good; the next run finishes the unit but cannot bring those back (retry_unaffordable)", "chapter 3 unit 0: withheld (hard_refusal)", } { if !strings.Contains(printed, want) { t.Fatalf("the refusal does not say %q", want) } } }