create([ 'username' => 'draw_pages_admin', 'name' => 'Draw QA', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($admin); return $admin->createToken('test', ['*'], now()->addDay())->plainTextToken; } test('admin draws index requires authentication', function (): void { $this->getJson('/api/v1/admin/draws')->assertUnauthorized(); }); test('admin draws index returns pagination', function (): void { Carbon::setTestNow(Carbon::parse('2026-05-09 12:00:00', 'UTC')); Draw::query()->create([ 'draw_no' => '20260509-001', 'business_date' => '2026-05-09', 'sequence_no' => 1, 'status' => 'pending', 'start_time' => now()->copy()->addHour(), 'close_time' => null, 'draw_time' => now()->copy()->addHours(2), 'cooling_end_time' => null, 'result_source' => null, 'current_result_version' => 0, 'settle_version' => 0, 'is_reopened' => false, ]); $token = mintAdminBearer(); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/draws?per_page=5') ->assertOk() ->assertJsonPath('data.meta.total', 1) ->assertJsonPath('data.items.0.draw_no', '20260509-001'); Carbon::setTestNow(); }); test('admin draws index can filter to draws with player bets', function (): void { $drawWithBet = Draw::query()->create([ 'draw_no' => '20260509-101', 'business_date' => '2026-05-09', 'sequence_no' => 101, 'status' => 'closed', 'start_time' => now()->subHour(), 'close_time' => now(), 'draw_time' => now()->addHour(), 'cooling_end_time' => null, 'result_source' => null, 'current_result_version' => 0, 'settle_version' => 0, 'is_reopened' => false, ]); Draw::query()->create([ 'draw_no' => '20260509-102', 'business_date' => '2026-05-09', 'sequence_no' => 102, 'status' => 'pending', 'start_time' => now()->addHour(), 'close_time' => now()->addHours(2), 'draw_time' => now()->addHours(3), 'cooling_end_time' => null, 'result_source' => null, 'current_result_version' => 0, 'settle_version' => 0, 'is_reopened' => false, ]); $player = Player::query()->create([ 'site_code' => 'main', 'site_player_id' => 'draw-filter-player', 'username' => 'draw_filter_player', 'nickname' => null, 'default_currency' => 'NPR', 'status' => 0, ]); $order = TicketOrder::query()->create([ 'order_no' => 'DRAW-FILTER-ORDER', 'player_id' => $player->id, 'draw_id' => $drawWithBet->id, 'currency_code' => 'NPR', 'total_bet_amount' => 1000, 'total_rebate_amount' => 0, 'total_actual_deduct' => 1000, 'total_estimated_payout' => 0, 'status' => 'paid', 'submit_source' => 'h5', 'client_trace_id' => 'draw-filter-trace', ]); TicketItem::query()->create([ 'ticket_no' => 'DRAW-FILTER-TICKET', 'order_id' => $order->id, 'player_id' => $player->id, 'draw_id' => $drawWithBet->id, 'provider_code' => 'SG', 'provider_name' => 'Singapore', 'original_number' => '1234', 'normalized_number' => '1234', 'play_code' => 'big', 'dimension' => 4, 'digit_slot' => null, 'bet_mode' => 'single', 'unit_bet_amount' => 1000, 'total_bet_amount' => 1000, 'rebate_rate_snapshot' => '0.0000', 'commission_rate_snapshot' => '0.0000', 'actual_deduct_amount' => 1000, 'win_amount' => 0, 'jackpot_win_amount' => 0, 'status' => 'paid', ]); $this->withHeader('Authorization', 'Bearer '.mintAdminBearer()) ->getJson('/api/v1/admin/draws?has_player_bets=1') ->assertOk() ->assertJsonPath('data.meta.total', 1) ->assertJsonPath('data.items.0.draw_no', '20260509-101'); }); test('admin draw show exposes hall preview status', function (): void { Carbon::setTestNow(Carbon::parse('2026-05-09 16:30:20', 'UTC')); $drawTime = Carbon::parse('2026-05-09 16:30:40', 'UTC'); $closeTime = $drawTime->copy()->subSeconds(30); $draw = Draw::query()->create([ 'draw_no' => '20260509-802', 'business_date' => '2026-05-09', 'sequence_no' => 802, 'status' => 'open', 'start_time' => $closeTime->copy()->subMinutes(5), 'close_time' => $closeTime, 'draw_time' => $drawTime, 'cooling_end_time' => null, 'result_source' => null, 'current_result_version' => 0, 'settle_version' => 0, 'is_reopened' => false, ]); $token = mintAdminBearer(); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/draws/'.$draw->id) ->assertOk() ->assertJsonPath('data.draw_no', '20260509-802') ->assertJsonPath('data.status', 'open') ->assertJsonPath('data.hall_preview_status', 'closing'); Carbon::setTestNow(); }); test('admin draw result batches lists items', function (): void { Carbon::setTestNow(Carbon::parse('2026-05-09 16:10:00', 'UTC')); $draw = Draw::query()->create([ 'draw_no' => '20260509-400', 'business_date' => '2026-05-09', 'sequence_no' => 400, 'status' => 'cooldown', 'start_time' => now()->copy()->subHour(), 'close_time' => now()->copy()->subMinutes(40), 'draw_time' => now()->copy()->subMinutes(20), 'cooling_end_time' => now()->copy()->addMinutes(10), 'result_source' => 'rng', 'current_result_version' => 1, 'settle_version' => 0, 'is_reopened' => false, ]); $batch = DrawResultBatch::query()->create([ 'draw_id' => $draw->id, 'result_version' => 1, 'source_type' => 'rng', 'rng_seed_hash' => hash('sha256', 'x'), 'raw_seed_encrypted' => null, 'status' => DrawResultBatchStatus::Published->value, 'created_by' => null, 'confirmed_by' => null, 'confirmed_at' => now(), ]); DrawResultItem::query()->create([ 'draw_id' => $draw->id, 'result_batch_id' => $batch->id, 'prize_type' => 'first', 'prize_index' => 0, 'number_4d' => '1234', 'suffix_3d' => '234', 'suffix_2d' => '34', 'head_digit' => 1, 'tail_digit' => 4, ]); $token = mintAdminBearer(); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/draws/'.$draw->id.'/result-batches') ->assertOk() ->assertJsonPath('data.draw_no', '20260509-400') ->assertJsonPath('data.batches.0.result_version', 1) ->assertJsonPath('data.batches.0.items.0.number_4d', '1234'); Carbon::setTestNow(); });