aboutsummaryrefslogtreecommitdiff
path: root/internal/game/payout_evidence_regression_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/payout_evidence_regression_test.go')
-rw-r--r--internal/game/payout_evidence_regression_test.go76
1 files changed, 76 insertions, 0 deletions
diff --git a/internal/game/payout_evidence_regression_test.go b/internal/game/payout_evidence_regression_test.go
new file mode 100644
index 0000000..b58bf9d
--- /dev/null
+++ b/internal/game/payout_evidence_regression_test.go
@@ -0,0 +1,76 @@
+package game
+
+// Regression: when the economy journal is unavailable, a payout paid
+// through the character file must record the round's stake-resolution
+// evidence in that same file. Otherwise a crash before the games' own
+// ResolveStakes call leaves the wager records open with checkpoint
+// balances that no longer match, and recovery refunds chip stakes the
+// paid settlement had already consumed — duplicating the round's value.
+
+import (
+ "testing"
+
+ "thehouseoficarus/internal/player"
+)
+
+func TestUnjournalablePayoutCrashDoesNotRefundChips(t *testing.T) {
+ h := newHarness(t)
+ h.seedAccount("alice")
+ p := h.seedCharacter("alice", "alice")
+ h.g.RunOnOwner(func() {
+ p.Credits = 100
+ p.SetInvSlot(0, &player.InventorySlot{ItemID: "chips", Quantity: 10})
+ w := casinoWallet{g: h.g, p: p}
+ if _, err := w.Wager(10); err != nil {
+ t.Error(err)
+ return
+ }
+ path := h.g.Economy.path
+ h.g.Economy.path = h.dir // persistent append failure
+ if err := w.PayCredits(20); err != nil {
+ t.Errorf("fallback payout failed: %v", err)
+ return
+ }
+ h.g.Economy.path = path
+ // Process loss BEFORE the game's own ResolveStakes call: the
+ // fallback payout itself must be the durable resolution.
+ h.g.recoverEconomy()
+ loaded, err := h.g.AccountStore.LoadCharacter("alice")
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if loaded.Credits != 120 || countChips(loaded) != 0 {
+ t.Errorf("recovered credits=%d chips=%d; want 120 credits and no chips (the paid settlement consumed the stake)", loaded.Credits, countChips(loaded))
+ }
+ if open := h.g.Economy.mustLoadOpen(t); len(open) != 0 {
+ t.Errorf("records left open after recovery: %+v", open)
+ }
+ })
+}
+
+// The stakes-open count is a proxy for "recovery did not silently refund":
+// with the fix the wager record settles through the file-side evidence.
+func TestUnjournalablePayoutEvidenceRecorded(t *testing.T) {
+ h := newHarness(t)
+ h.seedAccount("alice")
+ p := h.seedCharacter("alice", "alice")
+ h.g.RunOnOwner(func() {
+ p.Credits = 100
+ w := casinoWallet{g: h.g, p: p}
+ if _, err := w.Wager(10); err != nil {
+ t.Error(err)
+ return
+ }
+ path := h.g.Economy.path
+ h.g.Economy.path = h.dir
+ if err := w.PayCredits(20); err != nil {
+ t.Errorf("fallback payout failed: %v", err)
+ return
+ }
+ h.g.Economy.path = path
+ if len(p.ResolvedStakes) == 0 {
+ t.Error("fallback payout recorded no stake-resolution evidence; a crash before ResolveStakes would double-refund chips")
+ }
+ })
+}