54 lines
3.1 KiB
Go
54 lines
3.1 KiB
Go
package pgstore
|
|
|
|
import (
|
|
"regexp"
|
|
"testing"
|
|
)
|
|
|
|
// ledger_appendonly_test.go: the strongest money invariant this package states, held by something
|
|
// other than a sentence (register row PD-397).
|
|
//
|
|
// `Adjust` promises "a ledger row is never edited: the correction is another row, which is what
|
|
// makes the sum reproducible", and `appendLedger` promises "the two are never written apart, because
|
|
// a cache that can lag its source is a second answer about money". Both were true, and both were
|
|
// held ONLY by nobody having written the statement that breaks them.
|
|
//
|
|
// ⚠ WHAT THIS DOES NOT DO, and the register asked for a decision rather than a reflex: it does not
|
|
// add a trigger on `credit_ledger`. A trigger was measured against this tree and refused on two
|
|
// grounds, both concrete. It fires on CASCADE too, and deleting a user cascading away that user's
|
|
// ledger is the boundary of "append-only" that the migration itself declares — so the trigger would
|
|
// forbid the one edit the schema means to allow. And it would break
|
|
// `TestAReleaseWhoseKeyWasSpentIsRefusedRatherThanSilent`, whose whole fixture is deliberate surgery
|
|
// on the ledger to reach a state "no code path produces" (runs_test.go): a legitimate test, and one
|
|
// that must not be sacrificed to a guard against a risk that is not the code's.
|
|
//
|
|
// So the invariant is what its own doc comment says it is — the discipline of the CODE — and this is
|
|
// that discipline made checkable. What stays unheld is named rather than implied: a data migration,
|
|
// an operator's psql and a future tool all go around this package by construction, and no rule here
|
|
// reaches them.
|
|
//
|
|
// Mutation caught: any `update credit_ledger` or `delete from credit_ledger` added to this package,
|
|
// including inside a statement assembled from fragments — which is why it walks the SQL gate's
|
|
// extractor rather than grepping the files.
|
|
func TestTheLedgerIsAppendOnlyInTheCodeThatWritesIt(t *testing.T) {
|
|
stmts := collectSQL(t)
|
|
// The same floor the gate next door keeps, and for the same reason: an extractor that stopped
|
|
// seeing this package would turn this into a green light over nothing.
|
|
if len(stmts) < 140 {
|
|
t.Fatalf("the extractor found %d statements: it has stopped seeing this package, and a gate that checks nothing passes", len(stmts))
|
|
}
|
|
// ⚠ SCHEMA-QUALIFIED TOO. `update public.credit_ledger` is the same statement to Postgres and
|
|
// walked straight through the first version of this pattern, which matched the bare name only —
|
|
// a gate with a hole exactly the width of a schema prefix.
|
|
const table = `(?:[a-z_]+\.)?credit_ledger`
|
|
edits := regexp.MustCompile(`(?is)(update\s+` + table + `|delete\s+from\s+` + table + `)`)
|
|
for _, st := range stmts {
|
|
if m := edits.FindString(st.sql); m != "" {
|
|
t.Errorf("%s: this package edits the ledger (%q). A ledger row is never edited — the "+
|
|
"correction is another row — and that is what makes the sum of the ledger the "+
|
|
"reproducible answer about an account's money. The schema does NOT enforce this "+
|
|
"(PD-397); nothing but this rule does.\n%s", st.where, m, indent(st.sql))
|
|
}
|
|
}
|
|
t.Logf("%d statements checked; none edits the ledger", len(stmts))
|
|
}
|