aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/paycredits_atomic_test.go
blob: b08415e036397c9cda3ee043f449172a63281273 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package casino

import "testing"

// recordingWallet records the stake IDs passed to PayCredits so a winning
// spin's atomicity can be asserted directly.
type recordingWallet struct {
	testWallet
	payStakes []string
}

func (w *recordingWallet) PayCredits(amount int, stakeIDs ...string) error {
	w.payStakes = append(w.payStakes, stakeIDs...)
	return w.testWallet.PayCredits(amount, stakeIDs...)
}

// TestWinningSpinConsumesStakeInPayoutSave: a winning paid spin must pass its
// triggering stake to the SAME PayCredits call, so the credit and the stake
// consumption are one durable save. Resolving the stake in a separate later
// call leaves a crash window in which the payout is durable but the stake is
// still pending — startup would then refund it on top of the win.
func TestWinningSpinConsumesStakeInPayoutSave(t *testing.T) {
	m := newMachine(1, MachineConfig{ID: "slots", Game: GameSlots}, newTestRand())
	w := &recordingWallet{testWallet: testWallet{credits: 100}}
	m.join("alice")
	m.beginSpinLocked(10, w, "", "")
	wagerID := m.stakeID

	m.spin = &SlotSpin{Payout: 25}
	m.finishSpin()

	if len(w.payStakes) != 1 || w.payStakes[0] != wagerID {
		t.Fatalf("winning spin passed stakes %v to PayCredits; want [%q] (one atomic payout+resolve)", w.payStakes, wagerID)
	}
	if len(w.open) != 0 {
		t.Fatalf("winning spin left %d stake(s) pending: %+v", len(w.open), w.open)
	}
}