126 lines
6.8 KiB
Go
126 lines
6.8 KiB
Go
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)
|
||
}
|
||
}
|
||
}
|