aboutsummaryrefslogtreecommitdiff
path: root/internal/game/economy_recovery_regression_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/economy_recovery_regression_test.go')
-rw-r--r--internal/game/economy_recovery_regression_test.go77
1 files changed, 77 insertions, 0 deletions
diff --git a/internal/game/economy_recovery_regression_test.go b/internal/game/economy_recovery_regression_test.go
index 738dece..12df39b 100644
--- a/internal/game/economy_recovery_regression_test.go
+++ b/internal/game/economy_recovery_regression_test.go
@@ -282,3 +282,80 @@ func TestRecoveryPreservesAmbiguousPayout(t *testing.T) {
t.Fatalf("ambiguous payout must stay open for operator inspection: %+v", open)
}
}
+
+// TestRecoveryKeepsUnpaidSiblingsOpenAtCreditCap: two identical open payout
+// records where re-applying the second would exceed the economy cap must
+// settle only the sibling that was actually paid. The unpaid 20-credit
+// liability is never erased: its record stays open for operator inspection.
+func TestRecoveryKeepsUnpaidSiblingsOpenAtCreditCap(t *testing.T) {
+ h := newHarness(t)
+ h.seedAccount("alice", "pw", false)
+ p := h.seedCharacter("alice", "alice")
+ h.g.RunOnOwner(func() {
+ p.Credits = casino.MaxCredits - 30
+ if err := h.g.AccountStore.SaveCharacter(p); err != nil {
+ t.Error(err)
+ return
+ }
+ for i := 0; i < 2; i++ {
+ if _, err := h.g.Economy.start("payout", p, 0, 20, p.Credits, p.Credits+20, 0, 0); err != nil {
+ t.Error(err)
+ return
+ }
+ }
+ h.g.recoverEconomy()
+ })
+ loaded, err := h.g.AccountStore.LoadCharacter("alice")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if loaded.Credits != casino.MaxCredits-10 {
+ t.Fatalf("balance after capped recovery = %d, want %d (one payout applied)", loaded.Credits, casino.MaxCredits-10)
+ }
+ if open := h.g.Economy.loadOpen(); len(open) != 1 {
+ t.Fatalf("unpaid liability must stay open for operator inspection, open records: %+v", open)
+ }
+}
+
+// TestRecoveryRestoresChipsFromMixedRefundCrash: a mixed chips+credits
+// refund whose credit side is resolved by a later payout record but whose
+// chip side was never saved must still return the chips. The wager record
+// may be settled only once nothing is owed.
+func TestRecoveryRestoresChipsFromMixedRefundCrash(t *testing.T) {
+ h := newHarness(t)
+ h.seedAccount("alice", "pw", false)
+ p := h.seedCharacter("alice", "alice")
+ h.g.RunOnOwner(func() {
+ p.Credits = 100
+ p.SetInvSlot(0, &player.InventorySlot{ItemID: "chips", Quantity: 10})
+ if err := h.g.AccountStore.SaveCharacter(p); err != nil {
+ t.Error(err)
+ return
+ }
+ // Durable 20-credit (10 chips + 10 credits) wager: after the debit
+ // the file holds 90 credits and no chips.
+ w := casinoWallet{g: h.g, p: p}
+ if _, err := w.Wager(20); err != nil {
+ t.Error(err)
+ return
+ }
+ // Crash during RefundOutstanding, after the credit-side payout was
+ // journaled but before anything beyond the debit was durable.
+ if _, err := h.g.Economy.start("payout", p, 0, 10, 90, 100, 10, 10); err != nil {
+ t.Error(err)
+ return
+ }
+ h.g.recoverEconomy()
+ })
+ loaded, err := h.g.AccountStore.LoadCharacter("alice")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if loaded.Credits != 100 || countChips(loaded) != 10 {
+ t.Fatalf("mixed refund after recovery = (%d credits, %d chips), want (100, 10); open records: %+v",
+ loaded.Credits, countChips(loaded), h.g.Economy.loadOpen())
+ }
+ if open := h.g.Economy.loadOpen(); len(open) != 0 {
+ t.Fatalf("nothing may stay owed after recovery: %+v", open)
+ }
+}