'baccarat'], [ 'name' => 'Baccarat', 'icon' => 'baccarat', 'description' => 'Baccarat Game', 'enabled' => true, 'params' => [ 'min_bet' => 100, 'max_bet' => 50000, ], ] ); } public function test_can_get_current_round() { /** @var \App\Models\User $user */ $user = User::factory()->create(['jjb' => 3456]); $round = BaccaratRound::forceCreate([ 'status' => 'betting', 'bet_opens_at' => now(), 'bet_closes_at' => now()->addMinutes(1), 'total_bet_big' => 0, 'total_bet_small' => 0, 'total_bet_triple' => 0, 'bet_count' => 0, 'bet_count_big' => 0, 'bet_count_small' => 0, 'bet_count_triple' => 0, 'total_payout' => 0, ]); $response = $this->actingAs($user)->getJson(route('baccarat.current')); $response->assertStatus(200); $response->assertJsonStructure(['round' => ['id', 'status', 'bet_closes_at']]); $this->assertEquals($round->id, $response->json('round.id')); $this->assertSame(3456, $response->json('jjb')); } public function test_can_bet() { Event::fake(); /** @var \App\Models\User $user */ $user = User::factory()->create(['jjb' => 200]); $round = BaccaratRound::forceCreate([ 'status' => 'betting', 'bet_opens_at' => now(), 'bet_closes_at' => now()->addMinutes(1), 'total_bet_big' => 0, 'total_bet_small' => 0, 'total_bet_triple' => 0, 'bet_count' => 0, 'bet_count_big' => 0, 'bet_count_small' => 0, 'bet_count_triple' => 0, 'total_payout' => 0, ]); $response = $this->actingAs($user)->postJson(route('baccarat.bet'), [ 'round_id' => $round->id, 'bet_type' => 'big', 'amount' => 100, ]); $response->assertStatus(200); $response->assertJson(['ok' => true]); $this->assertEquals(100, $user->fresh()->jjb); $this->assertDatabaseHas('baccarat_bets', [ 'round_id' => $round->id, 'user_id' => $user->id, 'bet_type' => 'big', 'amount' => 100, ]); Event::assertDispatched(\App\Events\BaccaratPoolUpdated::class); } public function test_cannot_bet_out_of_range() { /** @var \App\Models\User $user */ $user = User::factory()->create(['jjb' => 200]); $round = BaccaratRound::forceCreate([ 'status' => 'betting', 'bet_opens_at' => now(), 'bet_closes_at' => now()->addMinutes(1), 'total_bet_big' => 0, 'total_bet_small' => 0, 'total_bet_triple' => 0, 'bet_count' => 0, 'bet_count_big' => 0, 'bet_count_small' => 0, 'bet_count_triple' => 0, 'total_payout' => 0, ]); $response = $this->actingAs($user)->postJson(route('baccarat.bet'), [ 'round_id' => $round->id, 'bet_type' => 'big', 'amount' => 50, // Less than min_bet ]); $response->assertStatus(200); $response->assertJson(['ok' => false]); } public function test_cannot_bet_twice_in_same_round() { /** @var \App\Models\User $user */ $user = User::factory()->create(['jjb' => 200]); $round = BaccaratRound::forceCreate([ 'status' => 'betting', 'bet_opens_at' => now(), 'bet_closes_at' => now()->addMinutes(1), 'total_bet_big' => 0, 'total_bet_small' => 0, 'total_bet_triple' => 0, 'bet_count' => 0, 'bet_count_big' => 0, 'bet_count_small' => 0, 'bet_count_triple' => 0, 'total_payout' => 0, ]); BaccaratBet::forceCreate([ 'round_id' => $round->id, 'user_id' => $user->id, 'bet_type' => 'big', 'amount' => 100, 'status' => 'pending', ]); $response = $this->actingAs($user)->postJson(route('baccarat.bet'), [ 'round_id' => $round->id, 'bet_type' => 'small', 'amount' => 100, ]); $response->assertStatus(200); $response->assertJson(['ok' => false]); } public function test_can_get_history() { /** @var \App\Models\User $user */ $user = User::factory()->create(); BaccaratRound::forceCreate([ 'status' => 'settled', 'bet_opens_at' => now()->subMinutes(2), 'bet_closes_at' => now()->subMinutes(1), 'settled_at' => now(), 'dice1' => 1, 'dice2' => 2, 'dice3' => 3, 'total_points' => 6, 'result' => 'small', 'total_bet_big' => 0, 'total_bet_small' => 0, 'total_bet_triple' => 0, 'bet_count' => 0, 'bet_count_big' => 0, 'bet_count_small' => 0, 'bet_count_triple' => 0, 'total_payout' => 0, ]); $response = $this->actingAs($user)->getJson(route('baccarat.history')); $response->assertStatus(200); $response->assertJsonStructure(['history']); $this->assertCount(1, $response->json('history')); } /** * 测试百家乐开奖后会给参与用户发送带右下角提示的私聊结算通知。 */ public function test_settlement_pushes_private_toast_notification_to_participant(): void { Queue::fake(); GameConfig::updateOrCreate( ['game_key' => 'baccarat'], [ 'name' => 'Baccarat', 'icon' => 'baccarat', 'description' => 'Baccarat Game', 'enabled' => true, // 将 3~18 全部设为庄家收割,确保本测试稳定命中亏损分支。 'params' => [ 'min_bet' => 100, 'max_bet' => 50000, 'kill_points' => range(3, 18), ], ] ); /** @var \App\Models\User $user */ $user = User::factory()->create(['jjb' => 400]); $round = BaccaratRound::forceCreate([ 'status' => 'betting', 'bet_opens_at' => now()->subMinute(), 'bet_closes_at' => now()->subSecond(), 'total_bet_big' => 100, 'total_bet_small' => 0, 'total_bet_triple' => 0, 'bet_count' => 1, 'bet_count_big' => 1, 'bet_count_small' => 0, 'bet_count_triple' => 0, 'total_payout' => 0, ]); BaccaratBet::forceCreate([ 'round_id' => $round->id, 'user_id' => $user->id, 'bet_type' => 'big', 'amount' => 100, 'payout' => 0, 'status' => 'pending', ]); (new CloseBaccaratRoundJob($round))->handle( app(\App\Services\UserCurrencyService::class), app(\App\Services\ChatStateService::class), app(\App\Services\BaccaratLossCoverService::class), ); $messages = Redis::lrange('room:1:messages', 0, -1); $privateMessage = collect($messages) ->map(fn (string $item) => json_decode($item, true)) ->first(fn (array $item) => ($item['from_user'] ?? null) === '系统' && ($item['to_user'] ?? null) === $user->username && ($item['toast_notification']['title'] ?? null) === '🎲 百家乐本局结算'); $this->assertNotNull($privateMessage); $this->assertTrue((bool) ($privateMessage['is_secret'] ?? false)); $this->assertStringContainsString('净输 100 金币', (string) ($privateMessage['content'] ?? '')); $this->assertStringContainsString('-100', (string) ($privateMessage['toast_notification']['message'] ?? '')); $this->assertSame('📉', $privateMessage['toast_notification']['icon'] ?? null); $this->assertSame('#ef4444', $privateMessage['toast_notification']['color'] ?? null); // 公屏开奖消息 + 参与者私聊通知,都应进入异步落库队列。 Queue::assertPushed(SaveMessageJob::class, 2); } }