create(['name' => 'Restore Workspace']); $tenant = Tenant::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => fake()->uuid(), 'name' => 'Restore Tenant', 'rbac_status' => 'ok', 'rbac_last_checked_at' => now(), ]); $tenant->makeCurrent(); if ($withProviderConnection) { ensureDefaultProviderConnection($tenant, 'microsoft'); } $policy = Policy::create([ 'tenant_id' => $tenant->id, 'external_id' => fake()->uuid(), 'policy_type' => 'deviceConfiguration', 'display_name' => 'Restore Policy', 'platform' => 'windows', ]); $backupSet = BackupSet::create([ 'tenant_id' => $tenant->id, 'name' => 'Restore Backup', 'status' => 'completed', 'item_count' => 1, ]); $backupItem = BackupItem::create([ 'tenant_id' => $tenant->id, 'backup_set_id' => $backupSet->id, 'policy_id' => $policy->id, 'policy_identifier' => $policy->external_id, 'policy_type' => $policy->policy_type, 'platform' => $policy->platform, 'payload' => ['id' => $policy->external_id], 'metadata' => ['displayName' => 'Restore Policy'], ]); $user = User::factory()->create([ 'email' => fake()->unique()->safeEmail(), 'name' => 'Restore Operator', ]); $user->tenants()->syncWithoutDetaching([ $tenant->getKey() => ['role' => 'owner'], ]); Filament::setTenant($tenant, true); return [$tenant, $backupSet, $backupItem, $user, $workspace]; } it('blocks restore execution before any operation run, restore run, job, or provider start is created', function (): void { Bus::fake(); [$tenant, $backupSet, $backupItem, $user] = seedOperationalRestoreExecutionContext(); OperationalControlActivation::factory()->workspaceScoped()->create([ 'control_key' => 'restore.execute', 'workspace_id' => (int) $tenant->workspace_id, 'reason_text' => 'Paused during restore safety review.', ]); $this->actingAs($user); Livewire::test(CreateRestoreRun::class) ->fillForm([ 'backup_set_id' => $backupSet->id, ]) ->goToNextWizardStep() ->fillForm([ 'scope_mode' => 'selected', 'backup_item_ids' => [$backupItem->id], ]) ->goToNextWizardStep() ->callFormComponentAction('check_results', 'run_restore_checks') ->goToNextWizardStep() ->callFormComponentAction('preview_diffs', 'run_restore_preview') ->goToNextWizardStep() ->fillForm([ 'is_dry_run' => false, 'acknowledged_impact' => true, 'tenant_confirm' => 'Restore Tenant', ]) ->call('create') ->assertNotified('Restore execution paused'); expect(RestoreRun::query()->count())->toBe(0) ->and(OperationRun::query()->where('type', 'restore.execute')->count())->toBe(0); $audit = AuditLog::query() ->where('action', AuditActionId::OperationalControlExecutionBlocked->value) ->latest('id') ->first(); expect($audit)->not->toBeNull() ->and($audit?->workspace_id)->toBe((int) $tenant->workspace_id) ->and($audit?->tenant_id)->toBe((int) $tenant->getKey()) ->and($audit?->status)->toBe('blocked') ->and($audit?->metadata['control_key'] ?? null)->toBe('restore.execute'); Bus::assertNotDispatched(ExecuteRestoreRunJob::class); }); it('does not retroactively mutate already accepted restore execution runs when a later pause is activated', function (): void { [$tenant, $backupSet, $backupItem, $user, $workspace] = seedOperationalRestoreExecutionContext(withProviderConnection: false); $operationRun = OperationRun::factory() ->forTenant($tenant) ->queued() ->create([ 'type' => 'restore.execute', 'outcome' => 'pending', 'initiator_name' => $user->name, 'context' => [ 'backup_set_id' => (int) $backupSet->getKey(), 'target_scope' => ['entra_tenant_id' => $tenant->graphTenantId()], ], ]); $restoreRun = RestoreRun::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'backup_set_id' => (int) $backupSet->getKey(), 'operation_run_id' => (int) $operationRun->getKey(), 'requested_by' => $user->email, 'is_dry_run' => false, 'status' => RestoreRunStatus::Queued->value, 'idempotency_key' => 'accepted-before-pause', 'requested_items' => [(int) $backupItem->getKey()], 'preview' => ['summary' => []], 'metadata' => ['confirmed_by' => $user->email], 'group_mapping' => [], ]); $platformUser = PlatformUser::factory()->create([ 'capabilities' => [ PlatformCapabilities::ACCESS_SYSTEM_PANEL, PlatformCapabilities::OPS_CONTROLS_MANAGE, ], 'is_active' => true, ]); Filament::setCurrentPanel('system'); Filament::bootCurrentPanel(); $this->actingAs($platformUser, 'platform'); Livewire::test(Controls::class) ->callAction('pause_restore_execute', data: [ 'scope_type' => 'workspace', 'workspace_id' => (int) $workspace->getKey(), 'reason_text' => 'Pause after the run was already accepted.', 'expires_at' => now()->addHour()->toDateTimeString(), ]) ->assertNotified('Restore execution paused'); expect($operationRun->fresh()) ->not->toBeNull() ->and($operationRun->fresh()?->status)->toBe('queued') ->and($operationRun->fresh()?->outcome)->toBe('pending'); expect($restoreRun->fresh()) ->not->toBeNull() ->and($restoreRun->fresh()?->status)->toBe(RestoreRunStatus::Queued->value) ->and((int) ($restoreRun->fresh()?->operation_run_id ?? 0))->toBe((int) $operationRun->getKey()); }); it('does not block restore execution for a different workspace when the pause is workspace-scoped', function (): void { Bus::fake(); $blockedWorkspace = Workspace::factory()->create(['name' => 'Blocked Workspace']); $allowedWorkspace = Workspace::factory()->create(['name' => 'Allowed Workspace']); [$blockedTenant] = seedOperationalRestoreExecutionContext(workspace: $blockedWorkspace); [$allowedTenant, $backupSet, $backupItem, $user] = seedOperationalRestoreExecutionContext(workspace: $allowedWorkspace); OperationalControlActivation::factory()->workspaceScoped()->create([ 'control_key' => 'restore.execute', 'workspace_id' => (int) $blockedTenant->workspace_id, 'reason_text' => 'Paused only for the blocked workspace.', ]); $this->actingAs($user); Filament::setTenant($allowedTenant, true); Livewire::test(CreateRestoreRun::class) ->fillForm([ 'backup_set_id' => $backupSet->id, ]) ->goToNextWizardStep() ->fillForm([ 'scope_mode' => 'selected', 'backup_item_ids' => [$backupItem->id], ]) ->goToNextWizardStep() ->callFormComponentAction('check_results', 'run_restore_checks') ->goToNextWizardStep() ->callFormComponentAction('preview_diffs', 'run_restore_preview') ->goToNextWizardStep() ->fillForm([ 'is_dry_run' => false, 'acknowledged_impact' => true, 'tenant_confirm' => 'Restore Tenant', ]) ->call('create') ->assertHasNoFormErrors(); $restoreRun = RestoreRun::query() ->where('tenant_id', (int) $allowedTenant->getKey()) ->latest('id') ->first(); $operationRun = OperationRun::query() ->where('tenant_id', (int) $allowedTenant->getKey()) ->where('type', 'restore.execute') ->latest('id') ->first(); expect($restoreRun)->not->toBeNull() ->and($operationRun)->not->toBeNull(); Bus::assertDispatched(ExecuteRestoreRunJob::class); });