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()) } }