TenantAtlas/apps/platform/tests/Feature/Restore/RestoreRunProviderStartTest.php
ahmido a089350f98
Some checks failed
Main Confidence / confidence (push) Failing after 49s
feat: unify provider-backed action dispatch gating (#255)
## Summary
- unify provider-backed action starts behind the shared provider dispatch gate and shared start-result presenter
- align tenant, onboarding, provider-connection, restore, directory, and monitoring surfaces with the same blocked, deduped, scope-busy, and accepted semantics
- include the spec kit artifacts for spec 216 and the regression fixes that brought the full suite back to green

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/RestoreRunIdempotencyTest.php tests/Feature/ExecuteRestoreRunJobTest.php tests/Feature/Restore/RestoreRunProviderStartTest.php tests/Feature/Hardening/ExecuteRestoreRunJobGateTest.php tests/Feature/Hardening/BlockedWriteAuditLogTest.php tests/Feature/Onboarding/OnboardingDraftLifecycleTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec177InventoryCoverageTruthSmokeTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact`

## Notes
- branch: `216-provider-dispatch-gate`
- commit: `34230be7`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #255
2026-04-20 06:52:38 +00:00

164 lines
5.2 KiB
PHP

<?php
use App\Filament\Resources\RestoreRunResource\Pages\CreateRestoreRun;
use App\Filament\Resources\RestoreRunResource\Pages\ListRestoreRuns;
use App\Jobs\ExecuteRestoreRunJob;
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\RestoreRun;
use App\Models\Tenant;
use App\Models\User;
use App\Support\Providers\ProviderReasonCodes;
use App\Support\RestoreRunStatus;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Bus;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function () {
putenv('INTUNE_TENANT_ID');
unset($_ENV['INTUNE_TENANT_ID'], $_SERVER['INTUNE_TENANT_ID']);
});
function seedRestoreStartContext(bool $withProviderConnection = true): array
{
$tenant = Tenant::create([
'tenant_id' => fake()->uuid(),
'name' => 'Restore Tenant',
'metadata' => [],
'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' => 'Device Config Policy',
'platform' => 'windows',
]);
$backupSet = BackupSet::create([
'tenant_id' => $tenant->id,
'name' => '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' => 'Backup Policy',
],
]);
$user = User::factory()->create([
'email' => 'restore@example.com',
'name' => 'Restore Operator',
]);
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
return [$tenant, $backupSet, $backupItem, $user];
}
it('starts restore execution with explicit provider connection context', function (): void {
Bus::fake();
[$tenant, $backupSet, $backupItem, $user] = seedRestoreStartContext();
$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')
->assertHasNoFormErrors();
$restoreRun = RestoreRun::query()->latest('id')->first();
$operationRun = OperationRun::query()
->where('tenant_id', (int) $tenant->getKey())
->where('type', 'restore.execute')
->latest('id')
->first();
expect($restoreRun)->not->toBeNull();
expect($restoreRun?->status)->toBe(RestoreRunStatus::Queued->value);
expect($operationRun)->not->toBeNull();
expect($operationRun?->context['provider_connection_id'] ?? null)->toBeInt();
Bus::assertDispatched(ExecuteRestoreRunJob::class, function (ExecuteRestoreRunJob $job) use ($restoreRun, $operationRun): bool {
return $job->restoreRunId === (int) $restoreRun?->getKey()
&& $job->providerConnectionId === ($operationRun?->context['provider_connection_id'] ?? null)
&& $job->operationRun?->is($operationRun);
});
});
it('blocks restore reruns before queue when no provider connection is available', function (): void {
Bus::fake();
[$tenant, $backupSet, $backupItem, $user] = seedRestoreStartContext(withProviderConnection: false);
$this->actingAs($user);
$run = RestoreRun::create([
'tenant_id' => $tenant->id,
'backup_set_id' => $backupSet->id,
'status' => 'failed',
'is_dry_run' => false,
'requested_items' => [$backupItem->id],
'group_mapping' => [],
]);
Livewire::test(ListRestoreRuns::class)
->callTableAction('rerun', $run);
expect(RestoreRun::query()->where('tenant_id', (int) $tenant->getKey())->count())->toBe(1);
$operationRun = OperationRun::query()
->where('tenant_id', (int) $tenant->getKey())
->where('type', 'restore.execute')
->latest('id')
->first();
expect($operationRun)->not->toBeNull();
expect($operationRun?->outcome)->toBe('blocked');
expect($operationRun?->context['reason_code'] ?? null)->toBe(ProviderReasonCodes::ProviderConnectionMissing);
Bus::assertNotDispatched(ExecuteRestoreRunJob::class);
});