textmachine/backend/internal/chunk/chunker_manifest_test.go

70 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package chunk
import (
"reflect"
"strings"
"testing"
"textmachine/backend/internal/lang"
)
// chunker_manifest_test.go: the seam SplitChunksWithChapters adds for the persisted manifest (backlog
// row 100). Two properties matter and neither is obvious from the signature: the split itself must not
// have changed, and the chapter texts returned must be index-aligned to the chapter NUMBERS the split
// emitted — a chapter that consumes no number must not consume a slot either, or every chapter after the
// first empty one is identified by its neighbour's text.
var manifestSeg = SegBudget{DraftBudgetOut: 1797, EditCeilingOut: 3200, FertCJK: 1.1978, FertOther: 0.3852}
func TestSplitChunksWithChaptersLeavesTheSplitUnchanged(t *testing.T) {
chapters := []string{
"Первая глава книги.",
" \n\t ", // whitespace-only: consumes NO chapter number
"Вторая глава книги.",
"",
strings.Repeat("Длинный абзац для нарезки. ", 200),
}
want := SplitChunks(chapters, manifestSeg, nil, nil)
got, kept, _ := SplitChunksWithChapters(chapters, manifestSeg, nil, nil)
if !reflect.DeepEqual(got, want) {
t.Fatal("SplitChunksWithChapters must produce the SAME cut as SplitChunks — the chunk boundaries decide what goes on the wire")
}
if len(kept) != 3 {
t.Fatalf("three chapters yield text, so three consume a number; got %d: %q", len(kept), kept)
}
// Index alignment: kept[n-1] must be the text of chapter n as the chunker numbered it.
if kept[0] != chapters[0] || kept[1] != chapters[2] || kept[2] != chapters[4] {
t.Fatalf("kept texts are not aligned to the emitted chapter numbers: %q", kept)
}
// …and every emitted chapter number has a slot.
highest := 0
for _, c := range got {
if c.Chapter > highest {
highest = c.Chapter
}
}
if highest != len(kept) {
t.Fatalf("the split emitted chapters up to %d but returned %d chapter texts", highest, len(kept))
}
}
// TestSplitChunksWithChaptersReturnsPreStripText: the text handed back is the chapter AS INGESTED, with
// its source header still in it. That is what makes an identity built on it survive a heading-rule edit —
// the rule changes what is STRIPPED and what title is rendered, not what ingest produced. Asserted with a
// rule ON and OFF over the same chapter, so the invariance is measured rather than assumed.
func TestSplitChunksWithChaptersReturnsPreStripText(t *testing.T) {
chapters := []string{"第一节:подзаголовок\nтело главы"}
rule := &lang.ChapterRule{Structure: lang.NewSourceStructure("第", "节"), Template: "Глава {n}"}
withRule, keptWith, _ := SplitChunksWithChapters(chapters, manifestSeg, rule, nil)
withoutRule, keptWithout, _ := SplitChunksWithChapters(chapters, manifestSeg, nil, nil)
if len(withRule) == 0 || len(withoutRule) == 0 {
t.Fatalf("fixture drifted: %d/%d chunks", len(withRule), len(withoutRule))
}
if withRule[0].Heading != "Глава 1" || strings.Contains(withRule[0].Text, "第一节") {
t.Fatalf("precondition: the rule must render a title and strip the marker, got heading=%q text=%q", withRule[0].Heading, withRule[0].Text)
}
if keptWith[0] != chapters[0] || keptWithout[0] != chapters[0] {
t.Fatalf("the chapter text must be returned AS INGESTED under both rules, got %q / %q", keptWith[0], keptWithout[0])
}
}