Automated PR created by Codex via Gitea API. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #461
269 lines
10 KiB
PHP
269 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Resources\RestoreRunResource;
|
|
use App\Filament\Resources\RestoreRunResource\Pages\CreateRestoreRun;
|
|
use App\Filament\Resources\RestoreRunResource\Pages\ViewRestoreRun;
|
|
use App\Filament\Resources\RestoreRunResource\Presenters\RestoreRunCreatePresenter;
|
|
use App\Models\BackupItem;
|
|
use App\Models\BackupSet;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
use App\Models\Policy;
|
|
use App\Models\ReviewPublicationResolutionCase;
|
|
use App\Models\ReviewPublicationResolutionStep;
|
|
use App\Models\RestoreRun;
|
|
use App\Services\Intune\RestoreService;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\RestoreReadinessResolution\RestoreReadinessAction;
|
|
use App\Support\RestoreReadinessResolution\RestoreReadinessReason;
|
|
use App\Support\RestoreReadinessResolution\RestoreReadinessState;
|
|
use App\Support\RestoreRunStatus;
|
|
use App\Support\RestoreSafety\RestoreSafetyResolver;
|
|
use Filament\Facades\Filament;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
use Mockery\MockInterface;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
/**
|
|
* @return array{operation_runs:int, review_publication_resolution_cases:int, review_publication_resolution_steps:int}
|
|
*/
|
|
function spec390GuidanceSideEffectCounts(): array
|
|
{
|
|
return [
|
|
'operation_runs' => OperationRun::query()->count(),
|
|
'review_publication_resolution_cases' => ReviewPublicationResolutionCase::query()->count(),
|
|
'review_publication_resolution_steps' => ReviewPublicationResolutionStep::query()->count(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array{operation_runs:int, review_publication_resolution_cases:int, review_publication_resolution_steps:int} $expected
|
|
*/
|
|
function spec390ExpectGuidanceSideEffectCounts(array $expected): void
|
|
{
|
|
expect(spec390GuidanceSideEffectCounts())->toBe($expected);
|
|
}
|
|
|
|
function spec390BackupSetWithItem(ManagedEnvironment $tenant): BackupSet
|
|
{
|
|
$policy = Policy::create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'external_id' => 'spec390-policy',
|
|
'policy_type' => 'deviceConfiguration',
|
|
'display_name' => 'Spec390 Policy',
|
|
'platform' => 'windows',
|
|
'metadata' => [],
|
|
]);
|
|
|
|
$backupSet = BackupSet::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'name' => 'Spec390 Backup',
|
|
'status' => 'completed',
|
|
'item_count' => 1,
|
|
]);
|
|
|
|
BackupItem::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'backup_set_id' => (int) $backupSet->getKey(),
|
|
'policy_id' => (int) $policy->getKey(),
|
|
'policy_identifier' => $policy->external_id,
|
|
'policy_type' => $policy->policy_type,
|
|
'platform' => $policy->platform,
|
|
'payload' => [
|
|
'id' => $policy->external_id,
|
|
'displayName' => 'Spec390 Policy',
|
|
'settings' => ['enabled' => true],
|
|
],
|
|
'assignments' => [],
|
|
'metadata' => [],
|
|
]);
|
|
|
|
return $backupSet;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
function spec390ReadyWizardData(BackupSet $backupSet): array
|
|
{
|
|
/** @var RestoreSafetyResolver $safety */
|
|
$safety = app(RestoreSafetyResolver::class);
|
|
|
|
$data = [
|
|
'backup_set_id' => (int) $backupSet->getKey(),
|
|
'scope_mode' => 'all',
|
|
'backup_item_ids' => [],
|
|
'group_mapping' => [],
|
|
'check_summary' => ['blocking' => 0, 'warning' => 0, 'safe' => 1],
|
|
'check_results' => [['code' => 'safe', 'severity' => 'safe']],
|
|
'checks_ran_at' => now('UTC')->toIso8601String(),
|
|
'preview_summary' => ['generated_at' => now('UTC')->toIso8601String(), 'policies_total' => 1],
|
|
'preview_diffs' => [['policy_identifier' => 'spec390-policy']],
|
|
'preview_ran_at' => now('UTC')->toIso8601String(),
|
|
'is_dry_run' => true,
|
|
];
|
|
|
|
$data['check_basis'] = $safety->checksBasisFromData($data);
|
|
$data['preview_basis'] = $safety->previewBasisFromData($data);
|
|
|
|
return RestoreRunResource::synchronizeRestoreSafetyDraft($data);
|
|
}
|
|
|
|
it('adds blocked readiness guidance to the restore create presenter', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
$this->actingAs($user);
|
|
ensureDefaultProviderConnection($tenant, 'microsoft');
|
|
|
|
$backupSet = spec390BackupSetWithItem($tenant);
|
|
$sideEffectCounts = spec390GuidanceSideEffectCounts();
|
|
|
|
$contract = RestoreRunCreatePresenter::contract(
|
|
data: [
|
|
'backup_set_id' => (int) $backupSet->getKey(),
|
|
'scope_mode' => 'all',
|
|
'backup_item_ids' => [],
|
|
'group_mapping' => [],
|
|
],
|
|
currentStep: 3,
|
|
compactFlow: true,
|
|
tenant: $tenant,
|
|
user: $user,
|
|
);
|
|
|
|
expect(data_get($contract, 'readinessGuidance.state'))->toBe(RestoreReadinessState::Blocked->value)
|
|
->and(data_get($contract, 'readinessGuidance.reason'))->toBe(RestoreReadinessReason::ChecksNotRun->value)
|
|
->and(data_get($contract, 'readinessGuidance.nextAction'))->toBe(RestoreReadinessAction::RunReadinessChecks->value)
|
|
->and(data_get($contract, 'readinessGuidance.actionSafetyCopy'))->toBe('This will not execute the restore.');
|
|
|
|
spec390ExpectGuidanceSideEffectCounts($sideEffectCounts);
|
|
});
|
|
|
|
it('adds ready-for-confirmation guidance to the restore create presenter', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create([
|
|
'rbac_status' => 'ok',
|
|
'rbac_last_checked_at' => now(),
|
|
]);
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
$this->actingAs($user);
|
|
ensureDefaultProviderConnection($tenant, 'microsoft');
|
|
|
|
$backupSet = spec390BackupSetWithItem($tenant);
|
|
|
|
$contract = RestoreRunCreatePresenter::contract(
|
|
data: spec390ReadyWizardData($backupSet),
|
|
currentStep: 5,
|
|
compactFlow: true,
|
|
tenant: $tenant,
|
|
user: $user,
|
|
);
|
|
|
|
expect(data_get($contract, 'readinessGuidance.state'))->toBe(RestoreReadinessState::ReadyForConfirmation->value)
|
|
->and(data_get($contract, 'readinessGuidance.nextAction'))->toBe(RestoreReadinessAction::ContinueToConfirmation->value)
|
|
->and(data_get($contract, 'readinessGuidance.actionSafetyCopy'))->toBe('The restore still requires final confirmation before execution.');
|
|
});
|
|
|
|
it('blocks create presenter readiness when execution prerequisites are unavailable', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create([
|
|
'rbac_status' => 'ok',
|
|
'rbac_last_checked_at' => now(),
|
|
]);
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
$this->actingAs($user);
|
|
ensureDefaultProviderConnection($tenant, 'microsoft', ensureCredential: false);
|
|
|
|
$backupSet = spec390BackupSetWithItem($tenant);
|
|
|
|
$contract = RestoreRunCreatePresenter::contract(
|
|
data: spec390ReadyWizardData($backupSet),
|
|
currentStep: 5,
|
|
compactFlow: true,
|
|
tenant: $tenant,
|
|
user: $user,
|
|
);
|
|
|
|
expect(data_get($contract, 'readinessGuidance.state'))->toBe(RestoreReadinessState::Blocked->value)
|
|
->and(data_get($contract, 'readinessGuidance.reason'))->toBe(RestoreReadinessReason::ExecutionPrerequisiteBlocked->value)
|
|
->and(data_get($contract, 'readinessGuidance.nextAction'))->toBe(RestoreReadinessAction::ReviewValidationBlockers->value)
|
|
->and(data_get($contract, 'wizardGate.execution_state'))->toBe('unavailable_until_prerequisites');
|
|
});
|
|
|
|
it('renders persisted restore-run readiness guidance on the view page', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
$this->actingAs($user);
|
|
Filament::setTenant($tenant, true);
|
|
|
|
$backupSet = spec390BackupSetWithItem($tenant);
|
|
$operationRun = OperationRun::factory()->forTenant($tenant)->create([
|
|
'type' => OperationRunType::RestoreExecute->value,
|
|
'status' => OperationRunStatus::Running->value,
|
|
]);
|
|
$restoreRun = RestoreRun::factory()->for($tenant, 'tenant')->for($backupSet)->create([
|
|
'status' => RestoreRunStatus::Running->value,
|
|
'operation_run_id' => (int) $operationRun->getKey(),
|
|
]);
|
|
$sideEffectCounts = spec390GuidanceSideEffectCounts();
|
|
|
|
Livewire::test(ViewRestoreRun::class, ['record' => $restoreRun->getKey()])
|
|
->assertSee('Restore readiness')
|
|
->assertSee('Restore execution is in progress.')
|
|
->assertSee('Next safe action: Open operation')
|
|
->assertSee('This guidance only opens existing execution evidence.');
|
|
|
|
spec390ExpectGuidanceSideEffectCounts($sideEffectCounts);
|
|
});
|
|
|
|
it('lets readonly users inspect persisted readiness guidance but keeps create mutations forbidden', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user] = createUserWithTenant($tenant, role: 'readonly');
|
|
$this->actingAs($user);
|
|
Filament::setTenant($tenant, true);
|
|
|
|
$backupSet = spec390BackupSetWithItem($tenant);
|
|
$restoreRun = RestoreRun::factory()->for($tenant, 'tenant')->for($backupSet)->previewOnly()->create();
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ViewRestoreRun::class, ['record' => $restoreRun->getKey()])
|
|
->assertSee('Restore readiness')
|
|
->assertSee("Restore can't continue yet.");
|
|
|
|
$this->actingAs($user)
|
|
->get(RestoreRunResource::getUrl('create', panel: 'admin', tenant: $tenant))
|
|
->assertForbidden();
|
|
});
|
|
|
|
it('preserves final execution confirmation and safety gates', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create([
|
|
'rbac_status' => 'ok',
|
|
'rbac_last_checked_at' => now(),
|
|
]);
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
$this->actingAs($user);
|
|
Filament::setTenant($tenant, true);
|
|
ensureDefaultProviderConnection($tenant, 'microsoft');
|
|
|
|
$backupSet = spec390BackupSetWithItem($tenant);
|
|
$sideEffectCounts = spec390GuidanceSideEffectCounts();
|
|
|
|
$this->mock(RestoreService::class, function (MockInterface $mock): void {
|
|
$mock->shouldNotReceive('preview');
|
|
$mock->shouldNotReceive('execute');
|
|
});
|
|
|
|
Livewire::test(CreateRestoreRun::class)
|
|
->fillForm([
|
|
...spec390ReadyWizardData($backupSet),
|
|
'is_dry_run' => false,
|
|
'acknowledged_impact' => false,
|
|
'tenant_confirm' => null,
|
|
])
|
|
->call('create')
|
|
->assertHasFormErrors(['acknowledged_impact']);
|
|
|
|
spec390ExpectGuidanceSideEffectCounts($sideEffectCounts);
|
|
});
|