create([ 'rbac_status' => 'ok', 'rbac_last_checked_at' => now(), ]); [$user] = createUserWithTenant(tenant: $tenant, role: 'owner'); ensureDefaultProviderConnection($tenant, 'microsoft'); /** @var RestoreSafetyResolver $resolver */ $resolver = app(RestoreSafetyResolver::class); $data = [ 'backup_set_id' => 50, 'scope_mode' => 'selected', 'backup_item_ids' => [4, 5], 'group_mapping' => ['group-a' => 'target-a'], 'check_summary' => ['blocking' => 0, 'warning' => 2, 'safe' => 1], 'check_results' => [['code' => 'warning', 'severity' => 'warning']], 'checks_ran_at' => now('UTC')->toIso8601String(), 'preview_summary' => ['generated_at' => now('UTC')->toIso8601String()], 'preview_diffs' => [['policy_identifier' => 'policy-1']], 'preview_ran_at' => now('UTC')->toIso8601String(), ]; $data['check_basis'] = $resolver->checksBasisFromData($data); $data['preview_basis'] = $resolver->previewBasisFromData($data); $data = \App\Filament\Resources\RestoreRunResource::synchronizeRestoreSafetyDraft($data); $assessment = $resolver->safetyAssessment($tenant, $user, $data); expect($assessment->state)->toBe(RestoreSafetyAssessment::STATE_READY_WITH_CAUTION) ->and($assessment->positiveClaimSuppressed)->toBeTrue() ->and($assessment->primaryNextAction)->toBe('review_warnings'); }); it('marks invalidated preview evidence as risky', function (): void { $tenant = Tenant::factory()->create([ 'rbac_status' => 'ok', 'rbac_last_checked_at' => now(), ]); [$user] = createUserWithTenant(tenant: $tenant, role: 'owner'); ensureDefaultProviderConnection($tenant, 'microsoft'); /** @var RestoreSafetyResolver $resolver */ $resolver = app(RestoreSafetyResolver::class); $currentData = [ 'backup_set_id' => 50, 'scope_mode' => 'selected', 'backup_item_ids' => [4, 6], 'group_mapping' => ['group-a' => 'target-a'], '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()], 'preview_diffs' => [['policy_identifier' => 'policy-1']], 'preview_ran_at' => now('UTC')->toIso8601String(), ]; $previousPreview = [ ...$currentData, 'backup_item_ids' => [4, 5], ]; $currentData['check_basis'] = $resolver->checksBasisFromData($currentData); $currentData['preview_basis'] = $resolver->previewBasisFromData($previousPreview); $currentData = \App\Filament\Resources\RestoreRunResource::synchronizeRestoreSafetyDraft($currentData); $assessment = $resolver->safetyAssessment($tenant, $user, $currentData); expect($assessment->state)->toBe(RestoreSafetyAssessment::STATE_RISKY) ->and($assessment->previewIntegrity->state)->toBe('invalidated') ->and($assessment->primaryNextAction)->toBe('regenerate_preview'); }); it('treats empty stored basis arrays as missing evidence instead of legacy stale', function (): void { /** @var RestoreSafetyResolver $resolver */ $resolver = app(RestoreSafetyResolver::class); $data = [ 'backup_set_id' => 50, 'scope_mode' => 'all', 'backup_item_ids' => [], 'group_mapping' => [], 'check_basis' => [], 'preview_basis' => [], 'check_summary' => [], 'preview_summary' => [], 'checks_ran_at' => null, 'preview_ran_at' => null, ]; expect($resolver->checksIntegrityFromData($data)->state)->toBe('not_run') ->and($resolver->previewIntegrityFromData($data)->state)->toBe('not_generated'); });