213 lines
6.8 KiB
PHP
213 lines
6.8 KiB
PHP
<?php
|
|
|
|
use App\Models\Draw;
|
|
use App\Models\Player;
|
|
use App\Models\RiskPool;
|
|
use App\Models\AdminUser;
|
|
use App\Models\PlayerWallet;
|
|
use App\Events\RiskSoldOutBroadcast;
|
|
use App\Events\RiskWarningBroadcast;
|
|
use Database\Seeders\CurrencySeeder;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Event;
|
|
use App\Events\BalanceUpdateBroadcast;
|
|
use App\Services\Ticket\RiskPoolService;
|
|
use Illuminate\Support\Facades\Broadcast;
|
|
use App\Events\PlayCatalogUpdatedBroadcast;
|
|
use Database\Seeders\LotterySettingsSeeder;
|
|
use Illuminate\Broadcasting\PrivateChannel;
|
|
use App\Services\Config\RiskCapStreamService;
|
|
use App\Services\Wallet\LotteryTransferService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use App\Services\Wallet\WalletBalanceRealtimeNotifier;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function (): void {
|
|
config(['broadcasting.default' => 'reverb']);
|
|
config(['lottery.main_site.wallet_api_url' => null]);
|
|
$this->seed(CurrencySeeder::class);
|
|
$this->seed(LotterySettingsSeeder::class);
|
|
});
|
|
|
|
test('balance update broadcasts only on the player private channel', function (): void {
|
|
$event = new BalanceUpdateBroadcast(42, 'NPR', 10_000, -500, 'bet', 1_234_567);
|
|
$channels = $event->broadcastOn();
|
|
|
|
expect($channels)->toHaveCount(1)
|
|
->and($channels[0])->toBeInstanceOf(PrivateChannel::class)
|
|
->and($channels[0]->name)->toBe('private-player.42');
|
|
});
|
|
|
|
test('player private channel auth allows only the matching bearer player', function (): void {
|
|
config([
|
|
'broadcasting.default' => 'reverb',
|
|
'broadcasting.connections.reverb.key' => 'test-reverb-key',
|
|
'broadcasting.connections.reverb.secret' => 'test-reverb-secret',
|
|
'broadcasting.connections.reverb.app_id' => 'test-reverb-app',
|
|
]);
|
|
Broadcast::purge('reverb');
|
|
require base_path('routes/channels.php');
|
|
|
|
$player = Player::query()->create([
|
|
'site_code' => 'test',
|
|
'site_player_id' => 'ws-auth-owner',
|
|
'default_currency' => 'NPR',
|
|
'status' => 0,
|
|
]);
|
|
$otherPlayer = Player::query()->create([
|
|
'site_code' => 'test',
|
|
'site_player_id' => 'ws-auth-other',
|
|
'default_currency' => 'NPR',
|
|
'status' => 0,
|
|
]);
|
|
$payload = [
|
|
'socket_id' => '1234.5678',
|
|
'channel_name' => 'private-player.'.$player->id,
|
|
];
|
|
|
|
$this->postJson('/api/broadcasting/auth', $payload)
|
|
->assertUnauthorized();
|
|
|
|
$this->withHeader('Authorization', 'Bearer dev:'.$otherPlayer->id)
|
|
->postJson('/api/broadcasting/auth', $payload)
|
|
->assertForbidden();
|
|
|
|
$this->withHeader('Authorization', 'Bearer dev:'.$player->id)
|
|
->postJson('/api/broadcasting/auth', $payload)
|
|
->assertOk()
|
|
->assertJsonStructure(['auth']);
|
|
});
|
|
|
|
test('wallet balance notifier dispatches balance update broadcast', function (): void {
|
|
Event::fake([BalanceUpdateBroadcast::class]);
|
|
|
|
$player = Player::query()->create([
|
|
'site_code' => 'test',
|
|
'site_player_id' => 'ws-p1',
|
|
'username' => null,
|
|
'nickname' => null,
|
|
'default_currency' => 'NPR',
|
|
'status' => 0,
|
|
]);
|
|
|
|
$wallet = PlayerWallet::query()->create([
|
|
'player_id' => $player->id,
|
|
'wallet_type' => 'lottery',
|
|
'currency_code' => 'NPR',
|
|
'balance' => 10_000,
|
|
'frozen_balance' => 0,
|
|
'status' => 0,
|
|
'version' => 0,
|
|
]);
|
|
|
|
app(WalletBalanceRealtimeNotifier::class)->notifyAfterMovement($wallet, -500, 'bet_deduct');
|
|
|
|
Event::assertDispatched(
|
|
BalanceUpdateBroadcast::class,
|
|
fn (BalanceUpdateBroadcast $event): bool => $event->playerId === $player->id
|
|
&& $event->currencyCode === 'NPR'
|
|
&& $event->balanceMinor === 10_000
|
|
&& $event->changeMinor === -500
|
|
&& $event->reason === 'bet',
|
|
);
|
|
});
|
|
|
|
test('risk pool acquire dispatches warning and sold out broadcasts', function (): void {
|
|
Event::fake([RiskWarningBroadcast::class, RiskSoldOutBroadcast::class]);
|
|
|
|
$draw = Draw::query()->create([
|
|
'draw_no' => '20260526-001',
|
|
'business_date' => '2026-05-26',
|
|
'sequence_no' => 1,
|
|
'status' => 'open',
|
|
'start_time' => now()->subHour(),
|
|
'close_time' => now()->addHour(),
|
|
'draw_time' => now()->addHours(2),
|
|
'cooling_end_time' => null,
|
|
'result_source' => null,
|
|
'current_result_version' => 0,
|
|
'settle_version' => 0,
|
|
'is_reopened' => false,
|
|
]);
|
|
|
|
RiskPool::query()->create([
|
|
'draw_id' => $draw->id,
|
|
'normalized_number' => '1234',
|
|
'total_cap_amount' => 1000,
|
|
'locked_amount' => 750,
|
|
'remaining_amount' => 250,
|
|
'sold_out_status' => 0,
|
|
'version' => 0,
|
|
]);
|
|
|
|
app(RiskPoolService::class)->acquire($draw->id, 'SG', null, [
|
|
['number_4d' => '1234', 'amount' => 250],
|
|
]);
|
|
|
|
Event::assertDispatched(
|
|
RiskWarningBroadcast::class,
|
|
fn (RiskWarningBroadcast $event): bool => $event->drawId === $draw->id
|
|
&& $event->drawNo === '20260526-001'
|
|
&& $event->normalizedNumber === '1234',
|
|
);
|
|
|
|
Event::assertDispatched(
|
|
RiskSoldOutBroadcast::class,
|
|
fn (RiskSoldOutBroadcast $event): bool => $event->drawId === $draw->id
|
|
&& $event->normalizedNumber === '1234',
|
|
);
|
|
});
|
|
|
|
test('risk cap publish dispatches play catalog updated broadcast', function (): void {
|
|
Event::fake([PlayCatalogUpdatedBroadcast::class]);
|
|
|
|
$admin = AdminUser::query()->create([
|
|
'username' => 'risk_cap_admin',
|
|
'name' => 'Risk Cap QA',
|
|
'email' => null,
|
|
'password' => Hash::make('secret-strong'),
|
|
'status' => 0,
|
|
]);
|
|
grantSuperAdminRole($admin);
|
|
|
|
$draft = app(RiskCapStreamService::class)->createDraft($admin, 'test', null);
|
|
app(RiskCapStreamService::class)->replaceItems($draft, [
|
|
[
|
|
'normalized_number' => '1234',
|
|
'cap_amount' => 1_000_000,
|
|
'cap_type' => 'default',
|
|
],
|
|
], $admin);
|
|
app(RiskCapStreamService::class)->publish($draft, $admin);
|
|
|
|
Event::assertDispatched(PlayCatalogUpdatedBroadcast::class);
|
|
});
|
|
|
|
test('transfer in dispatches balance update after success', function (): void {
|
|
Event::fake([BalanceUpdateBroadcast::class]);
|
|
|
|
$player = Player::query()->create([
|
|
'site_code' => 'test',
|
|
'site_player_id' => 'ws-p2',
|
|
'username' => null,
|
|
'nickname' => null,
|
|
'default_currency' => 'NPR',
|
|
'status' => 0,
|
|
]);
|
|
|
|
PlayerWallet::query()->create([
|
|
'player_id' => $player->id,
|
|
'wallet_type' => 'lottery',
|
|
'currency_code' => 'NPR',
|
|
'balance' => 0,
|
|
'frozen_balance' => 0,
|
|
'status' => 0,
|
|
'version' => 0,
|
|
]);
|
|
|
|
app(LotteryTransferService::class)->transferIn($player, 'NPR', 500, 'idem-ws-'.uniqid('', true));
|
|
|
|
Event::assertDispatched(BalanceUpdateBroadcast::class);
|
|
});
|