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]); $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(); $batch = DrawResultBatch::query()->where('draw_id', $draw->id)->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(); });