aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/hardening_casino_disconnect_test.go
blob: 6d2558164d7ae61ac72727b8489c36ca9925810c (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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package casino

import "testing"

// A disconnect must only forfeit a hand that is actually in flight. A stake
// placed in the betting window has had no card dealt for it, so refunding it
// reveals nothing; destroying it there would let an ordinary transient TCP
// drop swallow an undelt wager.

// TestBlackjackPreDealDisconnectRefundsStake: alice bets, the betting window
// is open (bob has not bet, no cards dealt), alice's socket drops. Her stake
// comes back, the wager record is resolved, and the round that follows pays
// nothing on top of the refund.
func TestBlackjackPreDealDisconnectRefundsStake(t *testing.T) {
	table := newBlackjackTable(1, TableConfig{ID: "blackjack", Game: GameBlackjack, MaxPlayers: 2, BettingTicks: 3}, newTestRand())
	table.join("alice")
	table.join("bob")
	wallet := &testWallet{credits: 100}
	table.bet("alice", "", 10, wallet)
	if table.phase != blackjackBetting {
		t.Fatalf("setup: expected the betting window (phase=%s)", table.phase)
	}
	if wallet.credits != 90 {
		t.Fatalf("setup: stake not committed (credits=%d)", wallet.credits)
	}

	events := table.markDisconnected("alice")
	if !hasMessage(events, "alice stands up") {
		t.Fatalf("missing disconnect event: %+v", events)
	}
	if got := wallet.credits + wallet.paid; got != 100 {
		t.Fatalf("pre-deal disconnect returned %d of 100; the undelt stake must be refunded", got)
	}
	if len(wallet.open) != 0 {
		t.Fatalf("refunded stake left outstanding: %+v", wallet.open)
	}

	// The window resolves with bob sitting out; nothing may pay the
	// refunded round afterwards.
	for i := 0; i < 10; i++ {
		table.tick()
	}
	if got := wallet.credits + wallet.paid; got != 100 {
		t.Fatalf("refunded pre-deal round paid again: balance %d", got)
	}
	if table.phase != blackjackWaiting {
		t.Fatalf("table did not reset after the window expired: phase=%s", table.phase)
	}
}

// TestBlackjackMidHandDisconnectForfeitsStake: the documented mid-hand
// forfeit policy is unchanged — cards are face up, so the stake stays with
// the house.
func TestBlackjackMidHandDisconnectForfeitsStake(t *testing.T) {
	table := newBlackjackTable(1, TableConfig{ID: "blackjack", Game: GameBlackjack, MaxPlayers: 2}, newTestRand())
	table.shoe = tableauShoe(
		card{rank: "10"}, card{rank: "7"}, // dealer
		card{rank: "10"}, card{rank: "8"}, // alice 18
		card{rank: "10"}, card{rank: "6"}, // bob 16
	)
	wallet := &testWallet{credits: 100}
	table.join("alice")
	table.join("bob")
	table.bet("alice", "", 10, wallet)
	table.bet("bob", "", 10, &testWallet{credits: 100})
	if table.phase != blackjackPlaying {
		t.Fatalf("setup: expected a dealt hand (phase=%s)", table.phase)
	}

	table.markDisconnected("alice")
	if wallet.credits != 90 || wallet.paid != 0 {
		t.Fatalf("mid-hand disconnect refunded a dealt hand: credits=%d paid=%d, want 90/0 (forfeit)", wallet.credits, wallet.paid)
	}
}

// TestBaccaratBettingWindowDisconnectRefundsStake: baccarat deals
// synchronously once every connected seat has bet, so an outstanding wager
// there has never had a card dealt for it either.
func TestBaccaratBettingWindowDisconnectRefundsStake(t *testing.T) {
	table := newBaccaratTable(1, TableConfig{ID: "baccarat", Game: GameBaccarat, MaxPlayers: 2, BettingTicks: 3}, newTestRand())
	table.join("alice")
	table.join("bob")
	wallet := &testWallet{credits: 100}
	table.bet("alice", baccaratSpotPlayer, 10, wallet)
	if table.phase != baccaratBetting {
		t.Fatalf("setup: expected the betting window (phase=%s)", table.phase)
	}

	table.markDisconnected("alice")
	if got := wallet.credits + wallet.paid; got != 100 {
		t.Fatalf("pre-deal baccarat disconnect returned %d of 100; want the stake refunded", got)
	}
	if len(wallet.open) != 0 {
		t.Fatalf("refunded baccarat stake left outstanding: %+v", wallet.open)
	}
	for i := 0; i < 10; i++ {
		table.tick()
	}
	if got := wallet.credits + wallet.paid; got != 100 {
		t.Fatalf("refunded pre-deal coup paid again: balance %d", got)
	}
}