Land the pin for the money read whose two figures the SQL gate cannot tell apart: swapping them stays green on the whole battery and inverts the numbers an operator reads during drift
This commit is contained in:
parent
c2af4b2d72
commit
72434cecc7
1 changed files with 63 additions and 0 deletions
63
platform/internal/pgstore/readaccount_test.go
Normal file
63
platform/internal/pgstore/readaccount_test.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package pgstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"textmachine/platform/internal/money"
|
||||
)
|
||||
|
||||
// readaccount_test.go: that the two money figures ReadAccount returns are not INTERCHANGEABLE.
|
||||
//
|
||||
// ⚠ Why this needs its own test, when the lifecycle test next door already reads both: that test —
|
||||
// and every other money test in this zone — asserts `Balance == LedgerSum`, because in the healthy
|
||||
// state they ARE equal. So the one thing none of them can see is the two being SWAPPED. Measured
|
||||
// rather than supposed: exchanging the two Scan targets in ReadAccount leaves the ENTIRE battery
|
||||
// green, all eighteen packages, and the mutant is not equivalent — under drift the clean code
|
||||
// answers Balance=4.00/LedgerSum=1.00 and the mutant answers 1.00/4.00 (register row PD-430).
|
||||
//
|
||||
// What it would cost in production is conditional but pointed. Drift is the one state ReadAccount
|
||||
// exists to reveal, and in exactly that state `tmplatformctl balance --user` would print the LEDGER
|
||||
// sum under the word "balance", while every decision taken on `Account.Balance` would be taken on
|
||||
// the ledger instead of the cache. The drift WARNING would still fire — the comparison survives a
|
||||
// swap — so the operator gets a correct alarm attached to two wrong numbers.
|
||||
//
|
||||
// The class is PD-394's and PD-376's: a money path that is right, and right only by nobody having
|
||||
// mistyped it, because no test distinguishes the values.
|
||||
//
|
||||
// Mutation caught: exchanging any two of the three Scan targets in ReadAccount.
|
||||
func TestTheAccountsThreeFiguresAreNotInterchangeable(t *testing.T) {
|
||||
s, ctx := testDB(t)
|
||||
now := time.Now().UTC()
|
||||
seedUser(t, s, ctx, "u1")
|
||||
seedBook(t, s, ctx, "bk1", "u1", 10)
|
||||
if _, err := s.Grant(ctx, "u1", 1_000_000, "test", "seed", "", now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A hold makes the THIRD figure distinct too, so a swap with `Reserved` cannot hide either.
|
||||
if err := s.Hold(ctx, "u1", "bk1", "r_read#1", 300_000, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// ⚠ DRIFT, manufactured on purpose: the cache is moved away from the ledger, which is the only
|
||||
// state in which these two figures differ — and therefore the only state that can tell them
|
||||
// apart. The zone's own fixture for PD-97 builds drift the same way, by hand, because no code
|
||||
// path produces it.
|
||||
exec(t, s, ctx, `update account_balances set balance_micro_usd = 4_000_000 where user_id = 'u1'`)
|
||||
|
||||
a, err := s.ReadAccount(ctx, "u1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := money.MicroUSD(4_000_000); a.Balance != want {
|
||||
t.Errorf("Balance is %s, want the CACHED balance %s — a reader that hands back the ledger sum "+
|
||||
"under this name reports the wrong money in the one state this function exists to reveal",
|
||||
a.Balance.USD(), want.USD())
|
||||
}
|
||||
if want := money.MicroUSD(700_000); a.LedgerSum != want {
|
||||
t.Errorf("LedgerSum is %s, want the sum of the LEDGER %s (a grant of 1.00 less a hold of 0.30)",
|
||||
a.LedgerSum.USD(), want.USD())
|
||||
}
|
||||
if want := money.MicroUSD(300_000); a.Reserved != want {
|
||||
t.Errorf("Reserved is %s, want the open hold %s", a.Reserved.USD(), want.USD())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue