package pgstore import ( "testing" "textmachine/platform/internal/ingest" "textmachine/platform/internal/money" ) // priced is twoChapters with a projection on it: a $2.00 book-level bound (the shipped arm's flat // figure), two chapters of unequal price, and one editor reservation. func priced() Structure { in := twoChapters("k1") in.Structure = ingest.StructureDetected in.Price = &ingest.BookPrice{ ExpectedUSD: 2_090_000, BookOnceUSD: 2_000_000, StepMaxUSD: 69_828, SourceChars: 3000, } // ⚠ The price lives on the UNIT and only there: a chapter's cost is the sum of the units still to // deliver, so a chapter-level copy would be a second carrier free to drift from it. in.Chapters[0].Units[0].Expected, in.Chapters[0].Units[0].SourceChars = 40_000, 1200 in.Chapters[0].Units[1].Expected, in.Chapters[0].Units[1].SourceChars = 20_000, 800 in.Chapters[1].Units[0].Expected, in.Chapters[1].Units[0].SourceChars = 30_000, 1000 return in } // The projection lands WITH the cut it was derived from, and it is read back as an order is priced: // the book's three figures, and the chapters still to be delivered with their own bills. // // ⚠ The per-chapter figure is PRO-RATED by what is left, and the division truncates — so the error // is downward, which is the ratified direction (D39.206: a hold that is short costs one top-up, a // long one freezes credit the buyer cannot spend elsewhere). func TestTheProjectionLandsWithTheCutAndPricesWhatIsLeft(t *testing.T) { s, ctx := testDB(t) book := readingBook(t, s, ctx, "u1") if err := s.SaveStructure(ctx, book, priced()); err != nil { t.Fatal(err) } got, err := s.ReadBookForOrder(ctx, "u1", book) if err != nil { t.Fatal(err) } if !got.Priced { t.Fatal("a book that carries a whole projection reads as unpriced") } if got.Expected != 2_090_000 || got.BookOnce != 2_000_000 || got.StepMax != 69_828 { t.Errorf("the book's figures: %+v", got) } if got.Structure != ingest.StructureDetected { t.Errorf("structure %q", got.Structure) } if len(got.Remaining) != 2 { t.Fatalf("%d chapters remaining, want 2", len(got.Remaining)) } if got.Remaining[0].Expected != 60_000 || got.Remaining[0].Units != 2 || got.Remaining[0].SourceChars != 2000 { t.Errorf("chapter 1: %+v", got.Remaining[0]) } if got.Remaining[0].ID == "" || got.Remaining[0].Number != 1 { t.Errorf("chapter 1 carries no identity to anchor an order on: %+v", got.Remaining[0]) } // ⛔ ONE OF CHAPTER ONE'S TWO UNITS COMES BACK DELIVERED — THE CHEAP ONE — and the chapter's // remainder is then the price of the unit that is LEFT, not half the chapter's total. // // The two units are $0.04 and $0.02, so the two arithmetics differ by half a cent here and by far // more on a real book: measured on a chapter holding a 100-rune unit at $0.001 beside a // 9900-rune one at $0.099, dividing by the COUNT quotes $0.050 against a real remainder of // $0.099 — a `covers_all` the engine then stops halfway through — and, with the expensive unit // delivered instead, quotes $0.050 against a real $0.001 and refuses a purchase the buyer can // afford. The engine prices per UNIT and this platform stores it; the count was never the // question. // // Written as a RESOLUTION ROW and not as a counter, because the rows are what the query reads and // the counters are derived from them: a fixture that moved only the counter would be describing a // state the materializer cannot produce. deliver := func(chapter, unit int) { t.Helper() if _, err := s.pool.Exec(ctx, ` insert into unit_resolutions (book_id, chapter, unit, wave, shipped, flagged, at) values ($1, $2, $3, 'edit', true, false, now())`, book, chapter, unit); err != nil { t.Fatal(err) } } deliver(1, 0) // the $0.04 unit of chapter one got, err = s.ReadBookForOrder(ctx, "u1", book) if err != nil { t.Fatal(err) } if len(got.Remaining) != 2 || got.Remaining[0].Units != 1 { t.Fatalf("a half-delivered chapter: %+v", got.Remaining) } if got.Remaining[0].Expected != 20_000 { t.Errorf("the remainder of chapter one is priced at %s, want the %s of the unit that is left "+ "(half the chapter would be %s, and that is the arithmetic this test exists to refuse)", got.Remaining[0].Expected.USD(), money.MicroUSD(20_000).USD(), money.MicroUSD(30_000).USD()) } // The TEXT that is left follows the same rule: what the screen says is «still to translate», not // the whole chapter. if got.Remaining[0].SourceChars != 800 { t.Errorf("the remainder of chapter one is %d runes, want the 800 of the unit that is left", got.Remaining[0].SourceChars) } // A chapter fully delivered leaves the order entirely: it is not for sale twice. deliver(1, 4) got, err = s.ReadBookForOrder(ctx, "u1", book) if err != nil { t.Fatal(err) } if len(got.Remaining) != 1 || got.Remaining[0].Number != 2 { t.Errorf("a delivered chapter is still on offer: %+v", got.Remaining) } } // A cut with NO projection lands as not priced, and nothing is invented in its place. The book is // still a book — its tree, its text and its reading surface are unaffected — it simply cannot be // SOLD until the engine has priced it. func TestACutWithoutAProjectionLandsAsUnpricedAndNotAsFree(t *testing.T) { s, ctx := testDB(t) book := readingBook(t, s, ctx, "u1") if err := s.SaveStructure(ctx, book, twoChapters("k1")); err != nil { t.Fatal(err) } got, err := s.ReadBookForOrder(ctx, "u1", book) if err != nil { t.Fatal(err) } if got.Priced { t.Fatalf("a cut with no projection reads as priced: %+v", got) } if got.Expected != 0 || got.StepMax != 0 { t.Errorf("figures were invented: %+v", got) } // The tree landed all the same. page, err := s.ListChapters(ctx, "u1", book, 0, "") if err != nil || len(page.Chapters) != 2 { t.Fatalf("the tree did not land beside the missing price: %+v (%v)", page, err) } } // ⛔ THE PROJECTION FOLLOWS THE CUT AND NEVER OUTLIVES IT. A price written a moment apart from the // tree is a price for a cut that may already be gone, so a re-cut that this build could not price // must leave NOTHING behind rather than yesterday's figures beside today's chapters: the stale pair // would sell a book that no longer exists, at a price nobody quoted. func TestARecutWithoutAPriceDoesNotLeaveTheOldOneStanding(t *testing.T) { s, ctx := testDB(t) book := readingBook(t, s, ctx, "u1") if err := s.SaveStructure(ctx, book, priced()); err != nil { t.Fatal(err) } recut := twoChapters("k2") // a different cut, and this time the engine could not price it if err := s.SaveStructure(ctx, book, recut); err != nil { t.Fatal(err) } got, err := s.ReadBookForOrder(ctx, "u1", book) if err != nil { t.Fatal(err) } if got.Priced { t.Fatalf("the previous cut's price survived a re-cut: %+v", got) } for _, c := range got.Remaining { if c.Expected != 0 { t.Errorf("chapter %d kept the previous cut's bill %s", c.Number, c.Expected.USD()) } } } // The honest character count reaches the library row, and the flag beside it says which number it // is. ⚠ `null` keeps its own meaning — «the book is still arriving» — because re-using it for «we do // not trust this number» would destroy a meaning the contract already promises (ratified D39.201 // §5б: a flag BESIDE the number, never a changed meaning for null). func TestTheEnginesCharacterCountReachesTheLibraryRowBesideItsAccuracy(t *testing.T) { s, ctx := testDB(t) book := readingBook(t, s, ctx, "u1") before, _, err := s.GetBook(ctx, "u1", book) if err != nil { t.Fatal(err) } if before.SourceChars != nil { t.Fatalf("a book with no manifest read carries an engine count: %v", *before.SourceChars) } if err := s.SaveStructure(ctx, book, priced()); err != nil { t.Fatal(err) } after, _, err := s.GetBook(ctx, "u1", book) if err != nil { t.Fatal(err) } if after.SourceChars == nil || *after.SourceChars != 3000 { t.Fatalf("the engine's rune count did not land: %v", after.SourceChars) } if after.Structure != ingest.StructureDetected { t.Errorf("the cut's provenance did not land: %q", after.Structure) } }