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 = &Projection{Expected: 2_090_000, BookOnce: 2_000_000, StepMax: 69_828} // ⚠ ON THE CUT, NOT ON THE PRICE — the store keeps the two apart on purpose, and the type makes // that the only way to say it (see Projection). in.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) } } // ⛔ THE HONEST CHARACTER COUNT SURVIVES A PRICE THIS BUILD COULD NOT READ, and it did not until an // audit asked. `source_chars` was written only when the whole projection passed its witness — so a // manifest that named the text perfectly and failed on a MONEY key cost the screen its character // count too, and «Знаков» fell back to the intake's approximation, which for an EPUB counts the runes // of a ZIP archive. The two facts are independent: one is about the text, the other about money. // // ⚠ Since Projection was cut down to money alone, writing the count off the price is no longer // EXPRESSIBLE — the field it would be read from does not exist. What this pin still catches is the // other half of the same mistake: gating the write on the price's arrival. // // Mutation caught: writing a null `source_chars` whenever `in.Price` is nil. func TestTheCharacterCountSurvivesAPriceThisBuildCouldNotRead(t *testing.T) { s, ctx := testDB(t) book := readingBook(t, s, ctx, "u1") in := priced() in.Price = nil // the money did not arrive… in.SourceChars = 3000 // …and the engine's count of the text did (priced() sets it; said again here // because this test is ABOUT that figure and a reader should not have to go look) if err := s.SaveStructure(ctx, book, in); err != nil { t.Fatal(err) } got, _, err := s.GetBook(ctx, "u1", book) if err != nil { t.Fatal(err) } if got.SourceChars == nil || *got.SourceChars != 3000 { t.Fatalf("the engine's rune count was discarded with the price: %v", got.SourceChars) } // …and the book is still refused for SALE, which is the other half: the two facts are independent // in both directions. order, err := s.ReadBookForOrder(ctx, "u1", book) if err != nil { t.Fatal(err) } if order.Priced { t.Error("a book whose price did not arrive reads as priced") } } // ⛔ WORK DELIVERED BEYOND THE ORDER'S BOUNDARY MUST NOT CANCEL WORK STILL OWED INSIDE IT. The // allowance handed to the engine is «ordered minus delivered», and counting the delivered book-WIDE // while counting the ordered only up to the boundary makes the two describe different sets: a run // ordered through chapter 2 whose chapters 1 and 3 came back delivered fell to its floor of one unit // and half-delivered what was bought. It costs no money — settlement is by fact — and it breaks «you // bought N», which is the promise this pack exists to make true. // // Mutation caught: `deliveredWithinOrder` counting the book instead of the order. func TestDeliveryBeyondTheOrderDoesNotCancelWorkOwedInsideIt(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) } // The order runs through chapter ONE (two units). Chapter TWO — beyond it — comes back delivered. var c1 string if err := s.pool.QueryRow(ctx, `select id from chapters where book_id = $1 and number = 1`, book).Scan(&c1); err != nil { t.Fatal(err) } if _, err := s.pool.Exec(ctx, ` update books set ordered_at = now(), ordered_through_chapter_id = $2, ordered_through_chapter_number = 1 where id = $1`, book, c1); err != nil { t.Fatal(err) } if _, err := s.pool.Exec(ctx, ` insert into unit_resolutions (book_id, chapter, unit, wave, shipped, flagged, at) values ($1, 2, 0, 'edit', true, false, now())`, book); err != nil { t.Fatal(err) } var ordered, delivered int var resolved bool if err := s.pool.QueryRow(ctx, ` select `+orderedUnits+`, `+orderResolved+`, `+deliveredWithinOrder+` from books b where b.id = $1`, book).Scan(&ordered, &resolved, &delivered); err != nil { t.Fatal(err) } if !resolved || ordered != 2 { t.Fatalf("the order through chapter one resolves to %d units (resolved %v), want 2", ordered, resolved) } if delivered != 0 { t.Errorf("a chapter delivered BEYOND the order counted as %d units of it: the allowance would "+ "fall to its floor and half-deliver what was bought", delivered) } }