textmachine/platform/internal/pgstore/bankreadout_test.go

313 lines
13 KiB
Go

package pgstore
import (
"errors"
"testing"
"textmachine/platform/internal/ingest"
)
// bankreadout_test.go: the storage half of engine backlog rows 224 and 253. Before it the read model
// took the read-out's `terms` alone, which at a signing stop is empty by construction — so the one
// screen that asks a person for a decision answered zero while 69 and 66 questions lay unread in the
// same document.
func stopReadOut(offered []ingest.OfferedTerm) ingest.Bank {
return ingest.Bank{
Boundary: ingest.BoundarySignatureRequested, RunID: "tm-stream-run_X-1",
Offered: &offered,
}
}
func intp(n int) *int { return &n }
// ⛔ THE THREE ABSENCES, and the pin exists because they look identical on a screen and are not: a book
// whose runs never published a read-out · a read-out carrying no such section · a section that is there
// and holds nothing. Only the last means "nothing to sign".
//
// Mutations this must catch: answering Unreadable false when no read-out exists; storing "the section
// was there" as a row count; dropping the `offered_present` column and deriving presence from rows.
func TestTheThreeWaysAStopCanHaveNoQuestionsAreNotOneAnswer(t *testing.T) {
s, ctx := testDB(t)
book := readingBook(t, s, ctx, "u1")
never, err := s.BankSigningStop(ctx, "u1", book)
if err != nil {
t.Fatal(err)
}
if !never.Unreadable {
t.Error("a book whose runs never published a read-out reported the stop as readable")
}
if err := s.SaveBank(ctx, book, ingest.Bank{Boundary: ingest.BoundarySignatureRequested}); err != nil {
t.Fatal(err)
}
noSection, err := s.BankSigningStop(ctx, "u1", book)
if err != nil {
t.Fatal(err)
}
if !noSection.Unreadable {
t.Error("a read-out that carries no section reported the stop as readable — an engine build older " +
"than the section is then indistinguishable from a stop that asked nothing")
}
if err := s.SaveBank(ctx, book, stopReadOut(nil)); err != nil {
t.Fatal(err)
}
empty, err := s.BankSigningStop(ctx, "u1", book)
if err != nil {
t.Fatal(err)
}
if empty.Unreadable {
t.Error("a section that is present and holds nothing was reported as unreadable")
}
if len(empty.Offered) != 0 {
t.Errorf("an empty section answered %d terms", len(empty.Offered))
}
}
// ⛔ The boundary is a CONDITION, not a label. The engine publishes this section at the signing stop and
// nowhere else, so rows standing at another boundary mean the document is not what this build takes it
// for — and serving them puts a stop on the screen that no run is holding.
//
// Mutation this must catch: reading `offered_present` alone and ignoring the boundary.
func TestTermsStandingAtABoundaryThatCannotCarryThemAreNotServed(t *testing.T) {
s, ctx := testDB(t)
book := readingBook(t, s, ctx, "u1")
offered := []ingest.OfferedTerm{{Src: "方源", Dst: "Фан Юань", Channel: ingest.ChannelBoth}}
// The premise the assertion stands on: the SAME rows at the stop's own boundary are served. Without
// it the test could pass on a build that serves nothing at all.
if err := s.SaveBank(ctx, book, stopReadOut(offered)); err != nil {
t.Fatal(err)
}
atStop, err := s.BankSigningStop(ctx, "u1", book)
if err != nil {
t.Fatal(err)
}
if atStop.Unreadable || len(atStop.Offered) != 1 {
t.Fatalf("the stop's own boundary did not serve its rows: %+v", atStop)
}
for _, boundary := range []string{
ingest.BoundaryRunStarted, ingest.BoundaryTermsTakenUnsigned,
ingest.BoundaryRunFinished, ingest.BoundaryBookReseeded, ingest.BoundaryUnknown,
} {
bank := stopReadOut(offered)
bank.Boundary = boundary
if err := s.SaveBank(ctx, book, bank); err != nil {
t.Fatal(err)
}
got, err := s.BankSigningStop(ctx, "u1", book)
if err != nil {
t.Fatal(err)
}
if !got.Unreadable || len(got.Offered) != 0 {
t.Errorf("boundary %s served %d terms as a stop's questions: %+v", boundary, len(got.Offered), got)
}
}
}
// ⛔ An absent number is not a zero, and the read model must not flatten what the reader kept apart.
// Measured: `conventions` is absent from every row of one bought stop read-out and present on every row
// of the other. `freq = 0` is the engine's own "only the draft side saw it"; a stated `confidence = 0`
// is the row a signer must look at, and the engine spells "the reply stated none" as a negative.
//
// Mutation this must catch: a `coalesce(freq, 0)` anywhere on the way out, or non-null columns.
func TestANumberTheReadOutNeverCarriedStaysAbsentThroughTheStore(t *testing.T) {
s, ctx := testDB(t)
book := readingBook(t, s, ctx, "u1")
if err := s.SaveBank(ctx, book, stopReadOut([]ingest.OfferedTerm{
{Src: "silent"},
{Src: "stated", Freq: intp(0), Spread: intp(0), Conventions: intp(0), Confidence: intp(0)},
{Src: "high-confidence", Confidence: intp(95)},
})); err != nil {
t.Fatal(err)
}
got, err := s.BankSigningStop(ctx, "u1", book)
if err != nil {
t.Fatal(err)
}
if len(got.Offered) != 3 {
t.Fatalf("stored 3 terms and read back %d", len(got.Offered))
}
silent := got.Offered[0]
for name, p := range map[string]*int{"freq": silent.Freq, "spread": silent.Spread,
"conventions": silent.Conventions, "confidence": silent.Confidence} {
if p != nil {
t.Errorf("%s was never carried and came back as %d", name, *p)
}
}
stated := got.Offered[1]
for name, p := range map[string]*int{"freq": stated.Freq, "spread": stated.Spread,
"conventions": stated.Conventions, "confidence": stated.Confidence} {
if p == nil || *p != 0 {
t.Errorf("%s was stated as 0 and came back as %v", name, p)
}
}
// ⚠ The engine's own «the reply stated none» is a NEGATIVE, and it no longer reaches this layer:
// the reader folds it to "not stated" at the seam, where every other crossing is folded. So what
// this store must carry is a STATED number, unrounded and unclamped — pinning the negative here
// would teach a path that was abolished.
if c := got.Offered[2].Confidence; c == nil || *c != 95 {
t.Errorf("a stated confidence came back as %v", c)
}
}
// The ranking IS the information — which row to read first — and it is the only address a term has, so
// the order the read-out published is the order that comes back. The list is replaced whole for the
// same reason: without an identity, no delta over it is expressible.
//
// Mutation this must catch: `order by src` or no order at all; writing the new rows before deleting the
// old, which leaves the tail of a longer previous read-out standing.
func TestTheStopsRankingIsTheOrderThatComesBackAndTheListIsReplacedWhole(t *testing.T) {
s, ctx := testDB(t)
book := readingBook(t, s, ctx, "u1")
if err := s.SaveBank(ctx, book, stopReadOut([]ingest.OfferedTerm{
{Src: "zeta"}, {Src: "alpha"}, {Src: "mu"},
})); err != nil {
t.Fatal(err)
}
got, err := s.BankSigningStop(ctx, "u1", book)
if err != nil {
t.Fatal(err)
}
want := []string{"zeta", "alpha", "mu"}
for i, src := range want {
if got.Offered[i].Src != src {
t.Fatalf("the stop's ranking came back re-sorted: %v", srcsOf(got.Offered))
}
}
if err := s.SaveBank(ctx, book, stopReadOut([]ingest.OfferedTerm{{Src: "only"}})); err != nil {
t.Fatal(err)
}
shorter, err := s.BankSigningStop(ctx, "u1", book)
if err != nil {
t.Fatal(err)
}
if len(shorter.Offered) != 1 || shorter.Offered[0].Src != "only" {
t.Errorf("a shorter read-out left the previous tail standing: %v", srcsOf(shorter.Offered))
}
}
// ⛔ Completeness is ABSENT until something measures it, and absence is not "the bank is complete". The
// engine makes the section absent rather than zeroed for exactly this reason: a zeroed one answers
// "consolidated 0, unanswered 0", which reads as "nothing is missing".
//
// Mutation this must catch: storing a zeroed section when the read-out carried none, or dropping the
// `consolidation_measured` column and testing the numbers instead.
func TestCompletenessIsAbsentUntilSomethingMeasuresItAndAbsenceIsNotCompleteness(t *testing.T) {
s, ctx := testDB(t)
book := readingBook(t, s, ctx, "u1")
if err := s.SaveBank(ctx, book, ingest.Bank{Boundary: ingest.BoundaryRunStarted}); err != nil {
t.Fatal(err)
}
page, err := s.ListBank(ctx, "u1", book, 0, "", nil)
if err != nil {
t.Fatal(err)
}
if page.Consolidation != nil {
t.Errorf("a read-out that measured nothing reported completeness: %+v", page.Consolidation)
}
measured := ingest.Bank{Boundary: ingest.BoundarySignatureRequested,
Consolidation: &ingest.Consolidation{RenderBatchesDropped: 5, ClassifyBatchesDropped: 2,
Consolidated: 38, Declined: 1, Unanswered: 47, NeverAsked: 7}}
if err := s.SaveBank(ctx, book, measured); err != nil {
t.Fatal(err)
}
page, err = s.ListBank(ctx, "u1", book, 0, "", nil)
if err != nil {
t.Fatal(err)
}
if page.Consolidation == nil {
t.Fatal("a measured section came back absent — the signer would read a partial bank as whole")
}
got := *page.Consolidation
want := BankConsolidation{RenderBatchesDropped: 5, ClassifyBatchesDropped: 2,
Consolidated: 38, Declined: 1, Unanswered: 47, NeverAsked: 7}
if got != want {
t.Errorf("the section was rewritten on the way through:\n got %+v\nwant %+v", got, want)
}
// It is a whole-bank aggregate and rides where they do: with the counts, on the first page only.
later, err := s.ListBank(ctx, "u1", book, 1, "cursor-that-is-not-the-first-page", nil)
if err == nil && later.Consolidation != nil {
t.Error("completeness rode a page that is not the first")
}
}
func srcsOf(ts []BankOfferedTerm) []string {
out := make([]string, 0, len(ts))
for _, t := range ts {
out = append(out, t.Src)
}
return out
}
// ⛔ A new `/v0` route is a new place to read somebody else's book from (API1 BOLA, the zone's base
// line). The ownership check is the FIRST thing this read does and the only thing between one user's
// stop and another's; a reader that scoped by book alone would serve it happily.
//
// Mutation this must catch: dropping the `bookScope` call, or scoping the query by book alone.
func TestAnotherUsersStopIsNotReadable(t *testing.T) {
s, ctx := testDB(t)
book := readingBook(t, s, ctx, "u1")
if err := s.SaveBank(ctx, book, stopReadOut([]ingest.OfferedTerm{{Src: "方源"}})); err != nil {
t.Fatal(err)
}
// The premise: the owner DOES see it. Without this the test could pass on a build that serves
// nobody, and the fixture would have lost the power to tell the two apart.
mine, err := s.BankSigningStop(ctx, "u1", book)
if err != nil || len(mine.Offered) != 1 {
t.Fatalf("the owner could not read their own stop: %v %+v", err, mine)
}
if _, err := s.BankSigningStop(ctx, "u2", book); !errors.Is(err, ErrNoBook) {
t.Errorf("a stranger's read of this book's stop answered %v, want ErrNoBook", err)
}
}
// ⛔ THE CLOSED-VOCABULARY GUARDS AT THE WRITE SEAM, pinned because an unpinned data decision is one a
// later edit removes silently — and here "silently" means a whole bank save failing on a CHECK.
//
// ⚠ `nullif($, ”)` in the statement does NOT do this work: it turns the EMPTY value into null, and
// the guard turns an UNKNOWN non-empty one into the empty value first. Without the guard an engine
// word that leaked this far — `banknote`, the very kind the contract renamed so it could not reach a
// client — hits the constraint and takes the bank down with it, at the one boundary a person is
// waiting at. Measured here rather than argued: the same value with the guard lands as "not named",
// and the row survives.
//
// Mutation this must catch: dropping either guard from the insert, or widening the CHECK.
func TestAnEngineWordThatLeakedThisFarIsStoredAsNotNamedRatherThanTakingTheBankDown(t *testing.T) {
s, ctx := testDB(t)
book := readingBook(t, s, ctx, "u1")
// Both engine vocabularies, both outside the contract's closed sets.
if err := s.SaveBank(ctx, book, stopReadOut([]ingest.OfferedTerm{
{Src: "leaked", Channel: "banknote", Kind: "ruby"},
})); err != nil {
t.Fatalf("an unknown vocabulary took the whole bank save down: %v", err)
}
got, err := s.BankSigningStop(ctx, "u1", book)
if err != nil {
t.Fatal(err)
}
if len(got.Offered) != 1 {
t.Fatalf("the row did not survive: %+v", got)
}
if got.Offered[0].Channel != "" {
t.Errorf("an engine word reached the read side as %q", got.Offered[0].Channel)
}
if got.Offered[0].Kind != "" {
t.Errorf("an engine kind reached the read side as %q", got.Offered[0].Kind)
}
// The premise the assertions stand on: a value INSIDE the vocabulary is still carried, or the
// fixture would pass on a build that stores nothing at all.
if err := s.SaveBank(ctx, book, stopReadOut([]ingest.OfferedTerm{
{Src: "named", Channel: ingest.ChannelBoth, Kind: "name"},
})); err != nil {
t.Fatal(err)
}
named, err := s.BankSigningStop(ctx, "u1", book)
if err != nil {
t.Fatal(err)
}
if named.Offered[0].Channel != ingest.ChannelBoth || named.Offered[0].Kind != "name" {
t.Errorf("a value inside the vocabulary was dropped too: %+v", named.Offered[0])
}
}