313 lines
13 KiB
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])
|
|
}
|
|
}
|