63 lines
3 KiB
Go
63 lines
3 KiB
Go
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())
|
|
}
|
|
}
|