feat: 彩票业务迁移并补全后台权限与代理结算体系
This commit is contained in:
@@ -17,6 +17,8 @@ use App\Models\DrawResultBatch;
|
||||
use App\Models\SettlementBatch;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Services\LotterySettings;
|
||||
use App\Events\DrawCountdownBroadcast;
|
||||
use App\Lottery\DrawResultBatchStatus;
|
||||
@@ -38,6 +40,8 @@ beforeEach(function (): void {
|
||||
'lottery.draw.require_manual_review' => false,
|
||||
'lottery.draw.cooldown_minutes' => 15,
|
||||
]);
|
||||
|
||||
Cache::flush();
|
||||
});
|
||||
|
||||
test('draw planner fills buffer rows with ordered draw_no', function (): void {
|
||||
@@ -1128,6 +1132,7 @@ test('lottery draw-tick command runs successfully', function (): void {
|
||||
|
||||
test('lottery hall-countdown dispatches draw.countdown when using reverb connection', function (): void {
|
||||
Event::fake([DrawCountdownBroadcast::class]);
|
||||
Cache::forget('lottery:hall:countdown:last-fingerprint');
|
||||
config([
|
||||
'broadcasting.default' => 'reverb',
|
||||
'broadcasting.connections.reverb.driver' => 'reverb',
|
||||
@@ -1156,6 +1161,56 @@ test('lottery hall-countdown dispatches draw.countdown when using reverb connect
|
||||
);
|
||||
});
|
||||
|
||||
test('lottery hall-countdown skips unchanged non-sync pulse but broadcasts on state change', function (): void {
|
||||
Cache::forget('lottery:hall:countdown:last-fingerprint');
|
||||
Event::fake([DrawCountdownBroadcast::class]);
|
||||
config([
|
||||
'broadcasting.default' => 'reverb',
|
||||
'broadcasting.connections.reverb.driver' => 'reverb',
|
||||
'lottery.realtime_hall_countdown_sync_interval_seconds' => 5,
|
||||
]);
|
||||
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-09 12:00:00', 'UTC'));
|
||||
|
||||
$draw = Draw::query()->create([
|
||||
'draw_no' => '20260509-002',
|
||||
'business_date' => '2026-05-09',
|
||||
'sequence_no' => 2,
|
||||
'status' => DrawStatus::Open->value,
|
||||
'start_time' => now()->subMinutes(5),
|
||||
'close_time' => now()->addMinutes(1),
|
||||
'draw_time' => now()->addMinutes(2),
|
||||
'cooling_end_time' => null,
|
||||
'result_source' => null,
|
||||
'current_result_version' => 0,
|
||||
'settle_version' => 0,
|
||||
'is_reopened' => false,
|
||||
]);
|
||||
|
||||
$this->artisan('lottery:hall-countdown')->assertSuccessful();
|
||||
Event::assertDispatchedTimes(DrawCountdownBroadcast::class, 1);
|
||||
|
||||
Event::fake([DrawCountdownBroadcast::class]);
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-09 12:00:01', 'UTC'));
|
||||
$this->artisan('lottery:hall-countdown')->assertSuccessful();
|
||||
Event::assertNotDispatched(DrawCountdownBroadcast::class);
|
||||
|
||||
Event::fake([DrawCountdownBroadcast::class]);
|
||||
$draw->forceFill([
|
||||
'status' => DrawStatus::Closing->value,
|
||||
'close_time' => Carbon::parse('2026-05-09 12:00:00', 'UTC'),
|
||||
])->save();
|
||||
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-09 12:00:02', 'UTC'));
|
||||
$this->artisan('lottery:hall-countdown')->assertSuccessful();
|
||||
Event::assertDispatched(
|
||||
DrawCountdownBroadcast::class,
|
||||
fn (DrawCountdownBroadcast $event): bool => ($event->data['status'] ?? null) === DrawStatus::Closing->value,
|
||||
);
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
test('hall snapshot skips stale pending draw and picks next upcoming row', function (): void {
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-25 18:00:00', 'UTC'));
|
||||
|
||||
@@ -1243,6 +1298,80 @@ test('hall snapshot switches to next bettable draw when cooldown ended', functio
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
test('hall snapshot reuses cached heavy fragments between second-level countdown builds', function (): void {
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-09 12:00:00', 'UTC'));
|
||||
|
||||
$draw = Draw::query()->create([
|
||||
'draw_no' => '20260509-001',
|
||||
'business_date' => '2026-05-09',
|
||||
'sequence_no' => 1,
|
||||
'status' => DrawStatus::Cooldown->value,
|
||||
'start_time' => now()->subMinutes(5),
|
||||
'close_time' => now()->subMinute(),
|
||||
'draw_time' => now()->subSeconds(30),
|
||||
'cooling_end_time' => now()->addMinutes(5),
|
||||
'result_source' => 'rng',
|
||||
'current_result_version' => 1,
|
||||
'settle_version' => 0,
|
||||
'is_reopened' => false,
|
||||
]);
|
||||
|
||||
RiskPool::query()->create([
|
||||
'draw_id' => $draw->id,
|
||||
'normalized_number' => '1234',
|
||||
'total_cap_amount' => 100_000,
|
||||
'locked_amount' => 90_000,
|
||||
'sold_out_status' => 0,
|
||||
]);
|
||||
|
||||
$batch = DrawResultBatch::query()->create([
|
||||
'draw_id' => $draw->id,
|
||||
'result_version' => 1,
|
||||
'source_type' => 'rng',
|
||||
'rng_seed_hash' => hash('sha256', 'fixture'),
|
||||
'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,
|
||||
]);
|
||||
|
||||
DB::enableQueryLog();
|
||||
app(DrawHallSnapshotBuilder::class)->build(now()->utc());
|
||||
$firstQueries = DB::getQueryLog();
|
||||
|
||||
DB::flushQueryLog();
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-09 12:00:01', 'UTC'));
|
||||
$payload = app(DrawHallSnapshotBuilder::class)->build(now()->utc());
|
||||
$secondQueries = DB::getQueryLog();
|
||||
DB::disableQueryLog();
|
||||
|
||||
$secondSql = collect($secondQueries)->pluck('query')->implode("\n");
|
||||
|
||||
expect($payload['draw_no'])->toBe('20260509-001')
|
||||
->and($payload['result_items'][0]['number_4d'] ?? null)->toBe('1234')
|
||||
->and($payload['risk_pool_alerts'][0]['normalized_number'] ?? null)->toBe('1234')
|
||||
->and(count($secondQueries))->toBeLessThan(count($firstQueries))
|
||||
->and($secondSql)->not->toContain('jackpot_pools')
|
||||
->and($secondSql)->not->toContain('risk_pools')
|
||||
->and($secondSql)->not->toContain('draw_result_batches')
|
||||
->and($secondSql)->not->toContain('draw_result_items');
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
test('draw tick dispatches draw.status_change when hall draw_no or status changes', function (): void {
|
||||
Event::fake([DrawStatusChangeBroadcast::class]);
|
||||
config([
|
||||
|
||||
Reference in New Issue
Block a user