- Draw相关控制器添加provider_code和provider_name字段支持 - 允许手动录入开奖批次时指定provider_code - RNG开奖批次生成支持多provider,分别生成对应批次数据 - DrawPublishService和DrawManualResultService中支持基于provider_code管理开奖版本和发布流程 - DrawResultViewService调整,支持按provider汇总及筛选开奖结果 - Settlement处理逻辑调整,按provider区分待结算票据及批次,分开结算 - 结算期间管理相关服务支持使用站点本地结算时区计算开账建议和日期处理 - 增加AdminSite的settlement_timezone字段及相关请求验证和数据存取支持 - 优化AdminSettlementPeriod相关代码,防止存在未结清票据时关账 - Ticket明细返回接口添加结算时区信息,提升用户时间体验 - 多处查询排序和版本号处理改进,保证多provider数据正确顺序与一致性
82 lines
3.1 KiB
PHP
82 lines
3.1 KiB
PHP
<?php
|
|
|
|
use App\Models\Draw;
|
|
use App\Models\BetProvider;
|
|
use App\Lottery\DrawStatus;
|
|
use App\Models\AdminUser;
|
|
use App\Models\DrawResultBatch;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use App\Services\Draw\DrawRngSeedDerivation;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
test('rng seed derivation is deterministic for fixed seed and draw', function (): void {
|
|
$seedHex = str_repeat('ab', 32);
|
|
$drawId = 42;
|
|
|
|
$a = DrawRngSeedDerivation::deriveNumber4d($seedHex, $drawId, 0);
|
|
$b = DrawRngSeedDerivation::deriveNumber4d($seedHex, $drawId, 0);
|
|
$c = DrawRngSeedDerivation::deriveNumber4d($seedHex, $drawId, 1);
|
|
|
|
expect($a)->toBe($b)
|
|
->and(strlen($a))->toBe(4)
|
|
->and($a)->not->toBe($c);
|
|
});
|
|
|
|
test('rng seed encrypt decrypt roundtrip preserves hex', function (): void {
|
|
$seedHex = DrawRngSeedDerivation::generateSeedHex();
|
|
$encrypted = DrawRngSeedDerivation::encryptSeedHex($seedHex);
|
|
|
|
expect(DrawRngSeedDerivation::decryptSeedHex($encrypted))->toBe($seedHex)
|
|
->and(DrawRngSeedDerivation::hashSeedHex($seedHex))->toBe(hash('sha256', $seedHex));
|
|
});
|
|
|
|
test('admin rng run stores encrypted seed and passes batch audit verification', function (): void {
|
|
config(['lottery.draw.require_manual_review' => true]);
|
|
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' => '20260525-rng-audit',
|
|
'business_date' => '2026-05-25',
|
|
'sequence_no' => 901,
|
|
'status' => DrawStatus::Closed->value,
|
|
'start_time' => now()->subMinutes(20),
|
|
'close_time' => now()->subMinutes(5),
|
|
'draw_time' => now()->subMinute(),
|
|
'cooling_end_time' => null,
|
|
'result_source' => null,
|
|
'current_result_version' => 0,
|
|
'settle_version' => 0,
|
|
'is_reopened' => false,
|
|
]);
|
|
|
|
$admin = AdminUser::query()->create([
|
|
'username' => 'rng_audit_admin',
|
|
'name' => 'RNG Audit',
|
|
'email' => null,
|
|
'password' => Hash::make('secret-strong'),
|
|
'status' => 0,
|
|
]);
|
|
grantSuperAdminRole($admin);
|
|
$token = $admin->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
|
|
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
|
->postJson("/api/v1/admin/draws/{$draw->id}/rng")
|
|
->assertOk();
|
|
|
|
$batches = DrawResultBatch::query()->where('draw_id', $draw->id)->orderBy('provider_code')->get();
|
|
expect($batches)->toHaveCount(2);
|
|
$batch = $batches->firstOrFail();
|
|
|
|
expect($batch->source_type)->toBe('rng')
|
|
->and($batch->rng_seed_hash)->not->toBeEmpty()
|
|
->and($batch->raw_seed_encrypted)->not->toBeEmpty()
|
|
->and($batch->items()->count())->toBe(23);
|
|
|
|
$seedHex = DrawRngSeedDerivation::decryptSeedHex((string) $batch->raw_seed_encrypted);
|
|
expect(DrawRngSeedDerivation::hashSeedHex($seedHex))->toBe($batch->rng_seed_hash)
|
|
->and(DrawRngSeedDerivation::verifyBatchAudit($batch->fresh(['items']), $draw->fresh()))->toBeTrue();
|
|
});
|