where('code', 'admin.config.play-versions.index') ->delete(); $this->artisan('lottery:admin-auth-audit') ->expectsOutputToContain('Admin authorization audit found') ->expectsOutputToContain('[route_coverage]') ->assertExitCode(1); }); test('admin authorization audit passes on the default authorization catalog', function (): void { $this->artisan('lottery:admin-auth-audit') ->expectsOutputToContain('Admin authorization audit passed.') ->assertExitCode(0); }); test('admin authorization sync can repair registry-backed api resources and pass audit', function (): void { DB::table('admin_api_resources') ->where('code', 'admin.currencies.destroy') ->delete(); $this->artisan('lottery:admin-auth-audit') ->expectsOutputToContain('admin.currencies.destroy') ->assertExitCode(1); $this->artisan('lottery:admin-auth-sync --audit') ->expectsOutputToContain('Admin authorization synced') ->expectsOutputToContain('Admin authorization audit passed.') ->assertExitCode(0); $resourceId = DB::table('admin_api_resources') ->where('code', 'admin.currencies.destroy') ->value('id'); expect($resourceId)->not->toBeNull(); $bindingCount = DB::table('admin_api_resource_bindings') ->where('api_resource_id', (int) $resourceId) ->count(); expect($bindingCount)->toBeGreaterThan(0); }); test('admin authorization audit detects role api resource drift', function (): void { $this->seed(AdminRbacAndUserSeeder::class); $resourceId = DB::table('admin_api_resources') ->where('code', 'admin.audit.index') ->value('id'); $roleId = DB::table('admin_roles') ->where('slug', 'finance') ->value('id'); expect($resourceId)->not->toBeNull(); expect($roleId)->not->toBeNull(); DB::table('admin_role_api_resources') ->where('role_id', (int) $roleId) ->where('api_resource_id', (int) $resourceId) ->delete(); $this->artisan('lottery:admin-auth-audit --skip-route-coverage') ->expectsOutputToContain('Missing role-resource grant') ->assertExitCode(1); });