240 lines
9.4 KiB
PHP
240 lines
9.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Resources\RestoreRunResource;
|
|
use App\Filament\Resources\RestoreRunResource\Pages\CreateRestoreRun;
|
|
use App\Models\BackupItem;
|
|
use App\Models\BackupSet;
|
|
use App\Models\Finding;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\Policy;
|
|
use App\Support\Baselines\BaselineCompareReasonCode;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\RestoreSafety\RestoreSafetyResolver;
|
|
use Livewire\Livewire;
|
|
|
|
it('renders baseline compare idle state with only the decision-card compare action visible', function (): void {
|
|
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
seedActiveBaselineForTenant($environment);
|
|
createInventorySyncOperationRunWithCoverage($environment, ['deviceConfiguration' => 'succeeded']);
|
|
|
|
$this->actingAs($user);
|
|
setAdminPanelContext($environment);
|
|
|
|
$component = baselineCompareLandingLivewire($environment)
|
|
->assertSee('Which baseline drift requires action?')
|
|
->assertSee('Needs attention')
|
|
->assertSee('Compare now')
|
|
->assertActionEnabled('compareNow')
|
|
->assertDontSee('OperationRun proof')
|
|
->assertDontSee('raw payload')
|
|
->assertDontSee('provider response');
|
|
|
|
expect(substr_count($component->html(), 'data-testid="baseline-compare-primary-action"'))->toBe(1);
|
|
});
|
|
|
|
it('renders baseline compare as one decision with technical proof collapsed', function (): void {
|
|
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
[$profile, $snapshot] = seedActiveBaselineForTenant($environment);
|
|
|
|
$run = seedBaselineCompareRun($environment, $profile, $snapshot, [
|
|
'reason_code' => BaselineCompareReasonCode::OverdueFindingsRemain->value,
|
|
'coverage' => [
|
|
'effective_types' => ['deviceConfiguration'],
|
|
'covered_types' => ['deviceConfiguration'],
|
|
'uncovered_types' => [],
|
|
'proof' => true,
|
|
],
|
|
]);
|
|
|
|
Finding::factory()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'finding_type' => Finding::FINDING_TYPE_DRIFT,
|
|
'scope_key' => 'baseline_profile:'.$profile->getKey(),
|
|
'severity' => Finding::SEVERITY_HIGH,
|
|
'status' => Finding::STATUS_NEW,
|
|
'source' => OperationRunType::BaselineCompare->value,
|
|
'baseline_operation_run_id' => (int) $run->getKey(),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
setAdminPanelContext($environment);
|
|
|
|
$component = baselineCompareLandingLivewire($environment)
|
|
->assertSee('Which baseline drift requires action?')
|
|
->assertSee('Needs attention')
|
|
->assertSee('Review drift findings')
|
|
->assertSee('Technical details and audit links')
|
|
->assertSee('View compare readiness details')
|
|
->assertDontSee('OperationRun proof')
|
|
->assertDontSee('raw diff')
|
|
->assertDontSee('raw payload')
|
|
->assertDontSee('provider response');
|
|
|
|
$html = $component->html();
|
|
|
|
expect($html)
|
|
->not->toContain('data-testid="baseline-compare-technical-details" open')
|
|
->not->toContain('data-testid="baseline-compare-readiness-disclosure" open')
|
|
->not->toContain('data-testid="baseline-compare-diagnostics" open');
|
|
});
|
|
|
|
it('renders restore source decision without duplicate readiness status vocabulary', function (): void {
|
|
[$user, $tenant, $backupSet] = spec398RestorePreviewFixture();
|
|
|
|
setAdminPanelContext($tenant);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(CreateRestoreRun::class)
|
|
->fillForm(['backup_set_id' => (int) $backupSet->getKey()])
|
|
->assertSee('Is this restore safe to continue?')
|
|
->assertSee('Needs attention')
|
|
->assertSee('Primary next action')
|
|
->assertSee('Continue to scope.')
|
|
->assertSee('Backup quality summary')
|
|
->assertSee('Ready')
|
|
->assertSee('View safety gates and proof')
|
|
->assertDontSee('Restore readiness')
|
|
->assertDontSee('Restore needs preparation.')
|
|
->assertDontSee('Ready for final confirmation.')
|
|
->assertDontSee("Restore can't continue yet.")
|
|
->assertDontSee('Degraded input')
|
|
->assertDontSee('Operation proof is complete');
|
|
});
|
|
|
|
it('renders restore preview with capped rows and secondary proof collapsed', function (): void {
|
|
[$user, $tenant, $backupSet, $backupItem] = spec398RestorePreviewFixture();
|
|
$data = spec398RestorePreviewData($backupSet, $backupItem);
|
|
|
|
setAdminPanelContext($tenant);
|
|
|
|
$component = Livewire::actingAs($user)
|
|
->test(CreateRestoreRun::class)
|
|
->set('data', $data)
|
|
->goToWizardStep(4)
|
|
->assertWizardCurrentStep(4)
|
|
->assertSee('Preview evidence')
|
|
->assertSee('Policies changed')
|
|
->assertSee('Requires review')
|
|
->assertSee('Validation blockers')
|
|
->assertSee('Next gate')
|
|
->assertSee('View secondary preview counts')
|
|
->assertSee('View safety gates and restore proof')
|
|
->assertSee('Spec398 Changed Policy 8')
|
|
->assertDontSee('Spec398 Changed Policy 9')
|
|
->assertDontSee('spec398 raw restore payload')
|
|
->assertDontSee('provider response payload');
|
|
|
|
$html = $component->html();
|
|
|
|
expect($html)
|
|
->toContain('data-testid="restore-run-preview-primary-metrics"')
|
|
->toContain('data-testid="restore-run-preview-secondary-metrics"')
|
|
->toContain('4 additional changed policies kept in detailed review.')
|
|
->not->toContain('data-testid="restore-run-preview-secondary-metrics" open')
|
|
->not->toContain('data-testid="restore-run-preview-evidence" open');
|
|
});
|
|
|
|
function spec398RestorePreviewFixture(): array
|
|
{
|
|
$tenant = ManagedEnvironment::factory()->create([
|
|
'rbac_status' => 'ok',
|
|
'rbac_last_checked_at' => now(),
|
|
]);
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
ensureDefaultProviderConnection($tenant, 'microsoft');
|
|
|
|
$policy = Policy::create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'external_id' => 'spec398-policy',
|
|
'policy_type' => 'deviceConfiguration',
|
|
'display_name' => 'Spec398 Restore Policy',
|
|
'platform' => 'windows',
|
|
]);
|
|
|
|
$backupSet = BackupSet::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'name' => 'Spec398 Restore Backup',
|
|
'status' => 'completed',
|
|
'item_count' => 1,
|
|
]);
|
|
|
|
$backupItem = 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' => $policy->display_name,
|
|
'settings' => ['encryption' => 'required'],
|
|
],
|
|
'metadata' => [
|
|
'displayName' => $policy->display_name,
|
|
],
|
|
'assignments' => [],
|
|
]);
|
|
|
|
return [$user, $tenant, $backupSet, $backupItem];
|
|
}
|
|
|
|
function spec398RestorePreviewData(BackupSet $backupSet, BackupItem $backupItem): array
|
|
{
|
|
/** @var RestoreSafetyResolver $resolver */
|
|
$resolver = app(RestoreSafetyResolver::class);
|
|
|
|
$previewDiffs = collect(range(1, 12))
|
|
->map(fn (int $index): array => [
|
|
'policy_identifier' => 'spec398-policy-'.$index,
|
|
'display_name' => 'Spec398 Changed Policy '.$index,
|
|
'policy_type' => 'deviceConfiguration',
|
|
'platform' => 'windows',
|
|
'action' => 'update',
|
|
'assignments_changed' => $index === 1,
|
|
'scope_tags_changed' => false,
|
|
'review_reason' => 'Spec398 preview change requires review.',
|
|
'review_action_label' => 'Review change',
|
|
'diff' => [
|
|
'summary' => ['added' => 0, 'removed' => 0, 'changed' => 1],
|
|
'changed' => ['setting' => ['before' => 'off', 'after' => 'on']],
|
|
'added' => [],
|
|
'removed' => [],
|
|
],
|
|
])
|
|
->all();
|
|
|
|
$data = [
|
|
'backup_set_id' => (int) $backupSet->getKey(),
|
|
'scope_mode' => 'selected',
|
|
'backup_item_ids' => [(int) $backupItem->getKey()],
|
|
'is_dry_run' => true,
|
|
'group_mapping' => [],
|
|
'check_summary' => ['blocking' => 0, 'warning' => 1, 'safe' => 2],
|
|
'check_results' => [
|
|
['code' => 'warning', 'severity' => 'warning', 'message' => 'Review assignment changes before execution.'],
|
|
['code' => 'safe', 'severity' => 'safe', 'message' => 'Scope fingerprint is current.'],
|
|
],
|
|
'checks_ran_at' => now('UTC')->toIso8601String(),
|
|
'preview_summary' => [
|
|
'generated_at' => now('UTC')->toIso8601String(),
|
|
'policies_total' => 12,
|
|
'policies_changed' => 12,
|
|
'assignments_changed' => 1,
|
|
'scope_tags_changed' => 0,
|
|
'raw_payload_marker' => 'spec398 raw restore payload',
|
|
'provider_response' => 'provider response payload',
|
|
],
|
|
'preview_diffs' => $previewDiffs,
|
|
'preview_ran_at' => now('UTC')->toIso8601String(),
|
|
];
|
|
|
|
$data['check_basis'] = $resolver->checksBasisFromData($data);
|
|
$data['preview_basis'] = $resolver->previewBasisFromData($data);
|
|
|
|
return RestoreRunResource::synchronizeRestoreSafetyDraft($data);
|
|
}
|