middleware()), fn (Closure $next, object $middleware): Closure => fn (object $job): mixed => $middleware->handle($job, $next), $terminal, ); return $pipeline($job); } it('blocks restore execution when the tenant becomes non-operable before start', function (): void { $tenant = \App\Models\Tenant::factory()->create([ 'rbac_status' => 'ok', 'rbac_last_checked_at' => now(), ]); $user = User::factory()->create(); createUserWithTenant(tenant: $tenant, user: $user, role: 'owner', ensureDefaultMicrosoftProviderConnection: false); $backupSet = BackupSet::factory()->for($tenant)->create([ 'name' => 'Backup', 'status' => 'completed', 'item_count' => 0, ]); $restoreRun = RestoreRun::create([ 'tenant_id' => $tenant->id, 'backup_set_id' => $backupSet->id, 'requested_by' => 'actor@example.com', 'is_dry_run' => false, 'status' => RestoreRunStatus::Queued->value, 'requested_items' => null, 'preview' => [], 'results' => null, 'metadata' => [], ]); $tenant->delete(); $tenant->refresh(); $operationRun = app(OperationRunService::class)->ensureRun( tenant: $tenant, type: 'restore.execute', inputs: [ 'restore_run_id' => $restoreRun->id, 'backup_set_id' => $backupSet->id, 'is_dry_run' => false, 'execution_authority_mode' => 'actor_bound', 'required_capability' => Capabilities::TENANT_MANAGE, ], initiator: $user, ); $tenant->forceFill([ 'status' => 'active', 'deleted_at' => null, ])->save(); $job = new ExecuteRestoreRunJob($restoreRun->id, 'actor@example.com', 'Actor', $operationRun); $terminalInvoked = false; runQueuedRestoreJobThroughMiddleware( $job, function (ExecuteRestoreRunJob $job) use (&$terminalInvoked): mixed { $terminalInvoked = true; return $job; }, ); $operationRun->refresh(); $restoreRun->refresh(); expect($terminalInvoked)->toBeFalse() ->and($operationRun->outcome)->toBe(OperationRunOutcome::Blocked->value) ->and($operationRun->context['reason_code'] ?? null)->toBe('tenant_not_operable') ->and($restoreRun->status)->toBe(RestoreRunStatus::Queued->value); }); it('allows restore execution when legitimacy still holds', function (): void { $tenant = \App\Models\Tenant::factory()->create([ 'rbac_status' => 'ok', 'rbac_last_checked_at' => now(), ]); $user = User::factory()->create(); createUserWithTenant(tenant: $tenant, user: $user, role: 'owner', ensureDefaultMicrosoftProviderConnection: false); $backupSet = BackupSet::factory()->for($tenant)->create([ 'name' => 'Backup', 'status' => 'completed', 'item_count' => 0, ]); $restoreRun = RestoreRun::create([ 'tenant_id' => $tenant->id, 'backup_set_id' => $backupSet->id, 'requested_by' => 'actor@example.com', 'is_dry_run' => false, 'status' => RestoreRunStatus::Queued->value, 'requested_items' => null, 'preview' => [], 'results' => null, 'metadata' => [], ]); $tenant->delete(); $tenant->refresh(); $operationRun = app(OperationRunService::class)->ensureRun( tenant: $tenant, type: 'restore.execute', inputs: [ 'restore_run_id' => $restoreRun->id, 'backup_set_id' => $backupSet->id, 'is_dry_run' => false, 'execution_authority_mode' => 'actor_bound', 'required_capability' => Capabilities::TENANT_MANAGE, ], initiator: $user, ); $job = new ExecuteRestoreRunJob($restoreRun->id, 'actor@example.com', 'Actor', $operationRun); $terminalInvoked = false; runQueuedRestoreJobThroughMiddleware( $job, function (ExecuteRestoreRunJob $job) use (&$terminalInvoked): mixed { $terminalInvoked = true; return $job; }, ); $operationRun->refresh(); expect($terminalInvoked)->toBeTrue() ->and($operationRun->outcome)->toBe(OperationRunOutcome::Succeeded->value); });