## Summary - finalize the restore create wizard productization across safety, validation, preview, and confirmation steps - refine the restore presenter output and Blade component rendering for clearer proof, scope, resolver, and execution-readiness states - add and update feature and browser coverage plus Spec 333 artifacts and screenshots ## Testing - Not run as part of this commit/PR task Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #403
388 lines
13 KiB
PHP
388 lines
13 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Resources\RestoreRunResource;
|
|
use App\Models\BackupItem;
|
|
use App\Models\BackupSet;
|
|
use App\Models\EntraGroup;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\Policy;
|
|
use App\Models\PolicyVersion;
|
|
use App\Models\User;
|
|
|
|
pest()->browser()->timeout(60_000);
|
|
|
|
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
|
|
|
function spec332RestoreWizardScreenshot(string $name): string
|
|
{
|
|
return 'spec332-restore-create-'.$name;
|
|
}
|
|
|
|
function spec332CopyBrowserScreenshot(string $name, ?string $targetFilename = null): void
|
|
{
|
|
$filename = spec332RestoreWizardScreenshot($name).'.png';
|
|
$source = base_path('tests/Browser/Screenshots/'.$filename);
|
|
$targetDirectory = repo_path('specs/332-product-process-flow-system-v1/artifacts/screenshots');
|
|
$targetFilename ??= $filename;
|
|
|
|
if (is_dir($targetDirectory) && ! is_writable($targetDirectory)) {
|
|
return;
|
|
}
|
|
|
|
if (! is_dir($targetDirectory)) {
|
|
@mkdir($targetDirectory, 0755, true);
|
|
}
|
|
|
|
if (! is_dir($targetDirectory) || ! is_writable($targetDirectory)) {
|
|
return;
|
|
}
|
|
|
|
if (! is_file($source)) {
|
|
$source = \Pest\Browser\Support\Screenshot::path($filename);
|
|
}
|
|
|
|
if (is_file($source)) {
|
|
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$targetFilename);
|
|
}
|
|
}
|
|
|
|
function spec332RestoreWizardScreenshotsLoginUrl(User $user, ManagedEnvironment $tenant, string $redirect = ''): string
|
|
{
|
|
return route('admin.local.smoke-login', array_filter([
|
|
'email' => $user->email,
|
|
'tenant' => $tenant->external_id,
|
|
'workspace' => $tenant->workspace->slug,
|
|
'redirect' => $redirect,
|
|
], static fn (?string $value): bool => filled($value)));
|
|
}
|
|
|
|
function spec332ScreenshotsTenant(): array
|
|
{
|
|
$tenant = ManagedEnvironment::factory()->create([
|
|
'rbac_status' => 'ok',
|
|
'rbac_last_checked_at' => now(),
|
|
]);
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
ensureDefaultProviderConnection($tenant, 'microsoft');
|
|
bindFailHardGraphClient();
|
|
|
|
return [$user, $tenant];
|
|
}
|
|
|
|
function spec332ScreenshotsRedirect(ManagedEnvironment $tenant): string
|
|
{
|
|
$redirectBase = RestoreRunResource::getUrl('create', panel: 'admin', tenant: $tenant);
|
|
|
|
return parse_url($redirectBase, PHP_URL_PATH) ?: '/admin';
|
|
}
|
|
|
|
function spec332ScreenshotsSelectBackupSet($page, BackupSet $backupSet): void
|
|
{
|
|
$selected = $page->script(<<<JS
|
|
(() => {
|
|
const select = document.getElementById('form.backup_set_id');
|
|
|
|
if (! select) {
|
|
return false;
|
|
}
|
|
|
|
select.value = '{$backupSet->getKey()}';
|
|
select.dispatchEvent(new Event('input', { bubbles: true }));
|
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
|
|
return true;
|
|
})()
|
|
JS);
|
|
|
|
expect($selected)->toBeTrue();
|
|
}
|
|
|
|
function spec332ScreenshotsWizardNext($page): void
|
|
{
|
|
$clicked = $page->script(<<<'JS'
|
|
(() => {
|
|
const footer = document.querySelector('.fi-sc-wizard-footer');
|
|
|
|
if (! footer) {
|
|
return false;
|
|
}
|
|
|
|
const nextTrigger = footer.querySelector('div[x-on\\:click*="requestNextStep"]');
|
|
|
|
if (! nextTrigger) {
|
|
return false;
|
|
}
|
|
|
|
if (nextTrigger.classList.contains('fi-hidden')) {
|
|
return false;
|
|
}
|
|
|
|
nextTrigger.scrollIntoView({ block: 'center' });
|
|
nextTrigger.click();
|
|
|
|
return true;
|
|
})()
|
|
JS);
|
|
|
|
expect($clicked)->toBeTrue();
|
|
}
|
|
|
|
function spec332ScreenshotsUsableBackupFixture(ManagedEnvironment $tenant): BackupSet
|
|
{
|
|
$policy = Policy::create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'external_id' => 'spec332-screenshots-policy-usable',
|
|
'policy_type' => 'deviceConfiguration',
|
|
'display_name' => 'Spec332 Screenshots Policy',
|
|
'platform' => 'windows',
|
|
]);
|
|
|
|
PolicyVersion::create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'policy_id' => (int) $policy->getKey(),
|
|
'version_number' => 1,
|
|
'policy_type' => $policy->policy_type,
|
|
'platform' => $policy->platform,
|
|
'captured_at' => now(),
|
|
'snapshot' => [
|
|
'foo' => 'current',
|
|
],
|
|
'metadata' => [],
|
|
'assignments' => [],
|
|
'scope_tags' => [],
|
|
]);
|
|
|
|
$backupSet = BackupSet::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'name' => 'Spec332 Screenshots Usable 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,
|
|
'captured_at' => now(),
|
|
'payload' => [
|
|
'foo' => 'backup',
|
|
'displayName' => 'Spec332 Screenshots Policy',
|
|
],
|
|
'assignments' => [],
|
|
'metadata' => [
|
|
'displayName' => 'Spec332 Screenshots Policy',
|
|
],
|
|
]);
|
|
|
|
return $backupSet;
|
|
}
|
|
|
|
function spec332ScreenshotsUnresolvedGroupBackupFixture(ManagedEnvironment $tenant): BackupSet
|
|
{
|
|
$policy = Policy::create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'external_id' => 'spec332-screenshots-policy-group',
|
|
'policy_type' => 'deviceConfiguration',
|
|
'display_name' => 'Spec332 Screenshots Group Mapping Policy',
|
|
'platform' => 'windows',
|
|
]);
|
|
|
|
$backupSet = BackupSet::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'name' => 'Spec332 Screenshots Group 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,
|
|
'captured_at' => now(),
|
|
'payload' => [
|
|
'foo' => 'backup',
|
|
'displayName' => 'Spec332 Screenshots Group Mapping Policy',
|
|
],
|
|
'assignments' => [[
|
|
'target' => [
|
|
'@odata.type' => '#microsoft.graph.groupAssignmentTarget',
|
|
'groupId' => '11111111-1111-1111-1111-111111111111',
|
|
'group_display_name' => 'Spec332 Missing Group',
|
|
],
|
|
]],
|
|
'metadata' => [
|
|
'displayName' => 'Spec332 Screenshots Group Mapping Policy',
|
|
],
|
|
]);
|
|
|
|
return $backupSet;
|
|
}
|
|
|
|
function spec332ScreenshotsMetadataOnlyFixture(ManagedEnvironment $tenant): BackupSet
|
|
{
|
|
$policy = Policy::create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'external_id' => 'spec332-screenshots-policy-metadata-only',
|
|
'policy_type' => 'deviceConfiguration',
|
|
'display_name' => 'Spec332 Screenshots Metadata Only Policy',
|
|
'platform' => 'windows',
|
|
]);
|
|
|
|
$backupSet = BackupSet::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'name' => 'Spec332 Screenshots Metadata-only 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,
|
|
'captured_at' => now(),
|
|
'payload' => [],
|
|
'assignments' => [],
|
|
'metadata' => [
|
|
'displayName' => 'Spec332 Screenshots Metadata Only Policy',
|
|
'snapshot_source' => 'metadata_only',
|
|
'warnings' => ['metadata only fallback'],
|
|
],
|
|
]);
|
|
|
|
return $backupSet;
|
|
}
|
|
|
|
it('captures step 1 screenshot with a degraded backup selected', function (): void {
|
|
[$user, $tenant] = spec332ScreenshotsTenant();
|
|
$backupSet = spec332ScreenshotsMetadataOnlyFixture($tenant);
|
|
|
|
$page = visit(spec332RestoreWizardScreenshotsLoginUrl($user, $tenant, spec332ScreenshotsRedirect($tenant)));
|
|
|
|
$page->resize(1920, 1200)
|
|
->waitForText('Select Backup Set');
|
|
|
|
spec332ScreenshotsSelectBackupSet($page, $backupSet);
|
|
|
|
$page->waitForText('Source not usable');
|
|
$page->script('window.scrollTo(0, 0);');
|
|
$page->screenshot(true, spec332RestoreWizardScreenshot('step-1-backup-selected'));
|
|
spec332CopyBrowserScreenshot('step-1-backup-selected', 'step-1-backup-selected.png');
|
|
});
|
|
|
|
it('captures step 2 screenshots for mapping summary + resolver expansion', function (): void {
|
|
[$user, $tenant] = spec332ScreenshotsTenant();
|
|
$backupSet = spec332ScreenshotsUnresolvedGroupBackupFixture($tenant);
|
|
|
|
EntraGroup::factory()->for($tenant)->create([
|
|
'entra_id' => 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
'display_name' => 'Spec332 Cached Target Group',
|
|
'last_seen_at' => now(),
|
|
]);
|
|
|
|
$page = visit(spec332RestoreWizardScreenshotsLoginUrl($user, $tenant, spec332ScreenshotsRedirect($tenant)));
|
|
|
|
$page->resize(1920, 1200)
|
|
->waitForText('Select Backup Set');
|
|
|
|
spec332ScreenshotsSelectBackupSet($page, $backupSet);
|
|
|
|
$page->waitForText('Continue to scope and resolve required mappings.');
|
|
spec332ScreenshotsWizardNext($page);
|
|
|
|
$page->waitForText('Resolve target mappings');
|
|
$page->script('window.scrollTo(0, 0);');
|
|
$page->screenshot(true, spec332RestoreWizardScreenshot('step-2-scope-default'));
|
|
spec332CopyBrowserScreenshot('step-2-scope-default', 'step-2-scope-default.png');
|
|
|
|
$page->script(<<<'JS'
|
|
(() => {
|
|
const section = document.querySelector('[data-testid="restore-run-mapping-resolver-section"]');
|
|
section?.querySelector('.fi-section-header')?.click();
|
|
})()
|
|
JS);
|
|
$page->waitForText('Hide mapping details');
|
|
|
|
$page->script('window.scrollTo(0, 0);');
|
|
$page->screenshot(true, spec332RestoreWizardScreenshot('step-2-resolver-expanded'));
|
|
spec332CopyBrowserScreenshot('step-2-resolver-expanded', 'step-2-resolver-expanded.png');
|
|
});
|
|
|
|
it('captures step 3 screenshot when validation is blocked', function (): void {
|
|
[$user, $tenant] = spec332ScreenshotsTenant();
|
|
$backupSet = spec332ScreenshotsMetadataOnlyFixture($tenant);
|
|
|
|
$page = visit(spec332RestoreWizardScreenshotsLoginUrl($user, $tenant, spec332ScreenshotsRedirect($tenant)));
|
|
|
|
$page->resize(1920, 1200)
|
|
->waitForText('Select Backup Set');
|
|
|
|
spec332ScreenshotsSelectBackupSet($page, $backupSet);
|
|
|
|
$page->waitForText('Source not usable');
|
|
spec332ScreenshotsWizardNext($page);
|
|
$page->waitForText('Define Restore Scope');
|
|
spec332ScreenshotsWizardNext($page);
|
|
$page->waitForText('Safety & Conflict Checks');
|
|
|
|
$page->waitForText('Run checks')
|
|
->click('Run checks')
|
|
->waitForText('Snapshot completeness');
|
|
|
|
spec332ScreenshotsWizardNext($page);
|
|
$page->waitForText('Validation blocked');
|
|
|
|
$page->script('window.scrollTo(0, 0);');
|
|
$page->screenshot(true, spec332RestoreWizardScreenshot('step-3-validation-blocked'));
|
|
spec332CopyBrowserScreenshot('step-3-validation-blocked', 'step-3-validation-blocked.png');
|
|
});
|
|
|
|
it('captures step 4 and 5 screenshots after preview generation', function (): void {
|
|
[$user, $tenant] = spec332ScreenshotsTenant();
|
|
$backupSet = spec332ScreenshotsUsableBackupFixture($tenant);
|
|
|
|
$page = visit(spec332RestoreWizardScreenshotsLoginUrl($user, $tenant, spec332ScreenshotsRedirect($tenant)));
|
|
|
|
$page->resize(1920, 1200)
|
|
->waitForText('Select Backup Set');
|
|
|
|
spec332ScreenshotsSelectBackupSet($page, $backupSet);
|
|
|
|
$page->waitForText('Source selected');
|
|
spec332ScreenshotsWizardNext($page);
|
|
$page->waitForText('Define Restore Scope');
|
|
spec332ScreenshotsWizardNext($page);
|
|
$page->waitForText('Safety & Conflict Checks');
|
|
|
|
$page->waitForText('Run checks')
|
|
->click('Run checks')
|
|
->waitForText('Validation passed');
|
|
|
|
spec332ScreenshotsWizardNext($page);
|
|
|
|
$page->waitForText('Generate preview')
|
|
->click('Generate preview')
|
|
->waitForText('Preview details');
|
|
|
|
$page->script('window.scrollTo(0, 0);');
|
|
$page->screenshot(true, spec332RestoreWizardScreenshot('step-4-preview-generated'));
|
|
spec332CopyBrowserScreenshot('step-4-preview-generated', 'step-4-preview-generated.png');
|
|
|
|
spec332ScreenshotsWizardNext($page);
|
|
$page->waitForText('Confirm & Execute');
|
|
|
|
$page->script('window.scrollTo(0, 0);');
|
|
$page->screenshot(true, spec332RestoreWizardScreenshot('step-5-confirm-ready'));
|
|
spec332CopyBrowserScreenshot('step-5-confirm-ready', 'step-5-confirm-ready.png');
|
|
});
|