create($attrs); $batch = DrawResultBatch::query()->create([ 'draw_id' => $draw->id, 'result_version' => 1, 'source_type' => 'rng', 'rng_seed_hash' => hash('sha256', $digit), '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' => str_repeat($digit, 4), 'suffix_3d' => str_repeat($digit, 3), 'suffix_2d' => str_repeat($digit, 2), 'head_digit' => (int) $digit, 'tail_digit' => (int) $digit, ]); return $draw->fresh(); } test('draw results index returns published draws with PRD shaped results', function (): void { $draw = seedMinimalPublishedDraw([ 'draw_no' => '20260509-111', 'business_date' => '2026-05-09', 'sequence_no' => 111, 'status' => DrawStatus::Cooldown->value, 'start_time' => now()->subHour(), 'close_time' => now()->subMinutes(45), 'draw_time' => now()->subMinutes(30), 'cooling_end_time' => now()->addMinutes(10), 'result_source' => 'rng', 'current_result_version' => 1, 'settle_version' => 0, 'is_reopened' => false, ], '8'); $batch = DrawResultBatch::query()->where('draw_id', $draw->id)->firstOrFail(); foreach (['second', 'third'] as $tier) { DrawResultItem::query()->create([ 'draw_id' => $draw->id, 'result_batch_id' => $batch->id, 'prize_type' => $tier, 'prize_index' => 0, 'number_4d' => $tier === 'second' ? '7777' : '6666', 'suffix_3d' => '777', 'suffix_2d' => '77', 'head_digit' => 7, 'tail_digit' => 7, ]); } $this->getJson('/api/v1/draw/results?size=5') ->assertOk() ->assertJsonPath('code', 0) ->assertJsonPath('data.items.0.draw_no', '20260509-111') ->assertJsonPath('data.items.0.results.1st', '8888') ->assertJsonPath('data.items.0.results.2nd', '7777') ->assertJsonPath('data.items.0.results.3rd', '6666'); }); test('draw result show includes neighbor draw numbers', function (): void { $t0 = now()->subHours(3); seedMinimalPublishedDraw([ 'draw_no' => '20260509-100', 'business_date' => '2026-05-09', 'sequence_no' => 100, 'status' => DrawStatus::Cooldown->value, 'start_time' => $t0, 'close_time' => $t0, 'draw_time' => $t0->copy()->addSecond(), 'cooling_end_time' => null, 'result_source' => 'rng', 'current_result_version' => 1, 'settle_version' => 0, 'is_reopened' => false, ], '1'); $t1 = now()->subHours(2); seedMinimalPublishedDraw([ 'draw_no' => '20260509-101', 'business_date' => '2026-05-09', 'sequence_no' => 101, 'status' => DrawStatus::Cooldown->value, 'start_time' => $t1, 'close_time' => $t1, 'draw_time' => $t1->copy()->addSecond(), 'cooling_end_time' => null, 'result_source' => 'rng', 'current_result_version' => 1, 'settle_version' => 0, 'is_reopened' => false, ], '2'); $t2 = now()->subHour(); seedMinimalPublishedDraw([ 'draw_no' => '20260509-102', 'business_date' => '2026-05-09', 'sequence_no' => 102, 'status' => DrawStatus::Cooldown->value, 'start_time' => $t2, 'close_time' => $t2, 'draw_time' => $t2->copy()->addSecond(), 'cooling_end_time' => null, 'result_source' => 'rng', 'current_result_version' => 1, 'settle_version' => 0, 'is_reopened' => false, ], '3'); $this->getJson('/api/v1/draw/results/20260509-101') ->assertOk() ->assertJsonPath('data.previous_draw_no', '20260509-100') ->assertJsonPath('data.next_draw_no', '20260509-102'); }); test('draw result show accepts sequence numbers longer than three digits', function (): void { seedMinimalPublishedDraw([ 'draw_no' => '20260509-1001', 'business_date' => '2026-05-09', 'sequence_no' => 1001, 'status' => DrawStatus::Settled->value, 'start_time' => now()->subHour(), 'close_time' => now()->subMinutes(45), 'draw_time' => now()->subMinutes(30), 'cooling_end_time' => null, 'result_source' => 'rng', 'current_result_version' => 1, 'settle_version' => 1, 'is_reopened' => false, ], '4'); $this->getJson('/api/v1/draw/results/20260509-1001') ->assertOk() ->assertJsonPath('code', 0) ->assertJsonPath('data.draw_no', '20260509-1001') ->assertJsonPath('data.results.1st', '4444'); }); test('draw results include all published provider batches for the same draw', function (): void { BetProvider::query()->updateOrCreate(['code' => 'SG'], ['name' => 'Singapore', 'is_enabled' => true, 'sort_order' => 10]); BetProvider::query()->updateOrCreate(['code' => 'MY'], ['name' => 'Malaysia', 'is_enabled' => true, 'sort_order' => 20]); $draw = Draw::query()->create([ 'draw_no' => '20260509-201', 'business_date' => '2026-05-09', 'sequence_no' => 201, 'status' => DrawStatus::Cooldown->value, 'start_time' => now()->subHour(), 'close_time' => now()->subMinutes(45), 'draw_time' => now()->subMinutes(30), 'cooling_end_time' => now()->addMinutes(10), 'result_source' => 'manual', 'current_result_version' => 1, 'settle_version' => 0, 'is_reopened' => false, ]); foreach ([['SG', 'Singapore', '1111'], ['MY', 'Malaysia', '2222']] as [$providerCode, $providerName, $firstNumber]) { $batch = DrawResultBatch::query()->create([ 'draw_id' => $draw->id, 'provider_code' => $providerCode, 'provider_name' => $providerName, 'result_version' => 1, 'source_type' => 'manual', 'rng_seed_hash' => null, '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' => $firstNumber, 'suffix_3d' => substr($firstNumber, -3), 'suffix_2d' => substr($firstNumber, -2), 'head_digit' => (int) substr($firstNumber, 0, 1), 'tail_digit' => (int) substr($firstNumber, 3, 1), ]); } $this->getJson('/api/v1/draw/results/20260509-201') ->assertOk() ->assertJsonPath('data.provider_code', 'SG') ->assertJsonPath('data.results.1st', '1111') ->assertJsonCount(2, 'data.provider_results') ->assertJsonPath('data.provider_results.0.provider_code', 'SG') ->assertJsonPath('data.provider_results.0.results.1st', '1111') ->assertJsonPath('data.provider_results.1.provider_code', 'MY') ->assertJsonPath('data.provider_results.1.results.1st', '2222'); });