shouldReceive('build')->andReturn([ 'overview' => array_replace_recursive([ 'overall' => 'ready', 'counts' => [ 'missing_application' => 0, 'missing_delegated' => 0, ], 'freshness' => [ 'is_stale' => false, 'last_refreshed_at' => now()->toIso8601String(), ], ], $overview), ]); }); } it('renders the recovery-readiness seam as a productization baseline', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $backupSet = BackupSet::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'name' => 'Productization baseline backup', 'item_count' => 1, 'completed_at' => now()->subMinutes(15), ]); BackupItem::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'backup_set_id' => (int) $backupSet->getKey(), 'payload' => ['id' => 'baseline-policy'], 'metadata' => [], 'assignments' => [], ]); $this->actingAs($user); Filament::setTenant($tenant, true); Livewire::test(RecoveryReadiness::class) ->assertSee('Backup posture') ->assertSee('Healthy'); }); it('surfaces customer-safe output honestly when evidence exists but no review pack is ready', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); mockTenantDashboardReadinessPermissions(); seedTenantReviewEvidence($tenant); $summary = app(TenantDashboardSummaryBuilder::class) ->build($tenant, $user) ->toArray(); $outputCard = collect($summary['readinessCards'])->firstWhere('key', 'customer_safe_output'); expect($outputCard) ->not->toBeNull() ->and($outputCard['status'])->toBe('Evidence available') ->and($outputCard['actionLabel'])->toBe('View export artifacts') ->and($outputCard['actionUrl'])->toBe(CustomerReviewWorkspace::tenantPrefilterUrl($tenant)); }); it('links ready customer-safe output directly to the latest review pack', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); mockTenantDashboardReadinessPermissions(); $snapshot = seedTenantReviewEvidence($tenant); $review = composeTenantReviewForTest($tenant, $user, $snapshot); $pack = ReviewPack::factory()->ready()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'tenant_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), ]); $summary = app(TenantDashboardSummaryBuilder::class) ->build($tenant, $user) ->toArray(); $outputCard = collect($summary['readinessCards'])->firstWhere('key', 'customer_safe_output'); expect($outputCard) ->not->toBeNull() ->and($outputCard['actionLabel'])->toBe('Open review pack') ->and($outputCard['actionUrl'])->toBe(ReviewPackResource::getUrl('view', ['record' => $pack], panel: 'tenant', tenant: $tenant)) ->and($outputCard['helperText'])->toBeNull(); }); it('uses required-permissions truth for provider blockage readiness summaries', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); mockTenantDashboardReadinessPermissions([ 'overall' => 'blocked', 'counts' => [ 'missing_application' => 2, 'missing_delegated' => 1, ], 'freshness' => [ 'is_stale' => true, ], ]); ProviderConnection::factory()->platform()->consentGranted()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'is_default' => true, 'verification_status' => 'blocked', 'last_health_check_at' => now()->subMinutes(12), 'display_name' => 'Microsoft Graph', ]); $summary = app(TenantDashboardSummaryBuilder::class) ->build($tenant, $user) ->toArray(); $providerPermissions = collect($summary['governanceStatus'])->firstWhere('label', 'Provider permissions'); $providerHealth = collect($summary['readinessCards'])->firstWhere('key', 'provider_health'); expect($providerPermissions) ->not->toBeNull() ->and($providerPermissions['value'])->toBe('Blocked') ->and($providerPermissions['tone'])->toBe('danger') ->and($providerPermissions['description'])->toContain('2 application permission(s) are still missing.') ->and($providerPermissions['description'])->toContain('The verification snapshot is stale.') ->and($providerHealth) ->not->toBeNull() ->and($providerHealth['headline'])->toBe('Microsoft Graph') ->and($providerHealth['status'])->toBe('Blocked') ->and($providerHealth['body'])->toContain('2 application permission(s) are still missing.') ->and(collect($providerHealth['meta'])->firstWhere('label', 'Missing permissions')['value'] ?? null)->toBe('3'); }); it('keeps readiness follow-up destinations tenant-scoped across review, evidence, output, and permissions surfaces', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); mockTenantDashboardReadinessPermissions(); $snapshot = seedTenantReviewEvidence($tenant); $review = composeTenantReviewForTest($tenant, $user, $snapshot); $summary = app(TenantDashboardSummaryBuilder::class) ->build($tenant, $user) ->toArray(); $currentReview = collect($summary['readinessCards'])->firstWhere('key', 'current_review'); $providerHealth = collect($summary['readinessCards'])->firstWhere('key', 'provider_health'); $outputCard = collect($summary['readinessCards'])->firstWhere('key', 'customer_safe_output'); $evidenceCoverage = collect($summary['governanceStatus'])->firstWhere('label', 'Evidence coverage'); $providerPermissions = collect($summary['governanceStatus'])->firstWhere('label', 'Provider permissions'); expect($currentReview) ->not->toBeNull() ->and($currentReview['actionUrl'])->toBe(TenantReviewResource::tenantScopedUrl('view', ['record' => $review], $tenant)) ->and($evidenceCoverage) ->not->toBeNull() ->and($evidenceCoverage['actionUrl'])->toBe(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], panel: 'tenant', tenant: $tenant)) ->and($outputCard) ->not->toBeNull() ->and($outputCard['actionUrl'])->toBe(CustomerReviewWorkspace::tenantPrefilterUrl($tenant)) ->and($providerHealth) ->not->toBeNull() ->and($providerHealth['actionUrl'])->toBe(RequiredPermissionsLinks::requiredPermissions($tenant)) ->and($providerPermissions) ->not->toBeNull() ->and($providerPermissions['actionUrl'])->toBe(RequiredPermissionsLinks::requiredPermissions($tenant)); }); it('surfaces current-review progress only from repo-real review summary metrics', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); mockTenantDashboardReadinessPermissions(); Finding::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => Finding::STATUS_RESOLVED, 'resolved_reason' => Finding::RESOLVE_REASON_REMEDIATED, 'resolved_at' => now(), ]); Finding::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => Finding::STATUS_RISK_ACCEPTED, ]); $snapshot = seedTenantReviewEvidence($tenant, findingCount: 1, driftCount: 0); $review = composeTenantReviewForTest($tenant, $user, $snapshot); $reviewSummary = is_array($review->summary) ? $review->summary : []; $completedSections = (int) ($reviewSummary['section_state_counts']['complete'] ?? 0); $totalSections = max(1, (int) ($reviewSummary['section_count'] ?? 0)); $reviewCompletionLabel = sprintf( '%d/%d (%d%%)', $completedSections, $totalSections, (int) round(($completedSections / $totalSections) * 100), ); $summary = app(TenantDashboardSummaryBuilder::class) ->build($tenant, $user) ->toArray(); $currentReview = collect($summary['readinessCards'])->firstWhere('key', 'current_review'); $progress = collect($currentReview['progress'] ?? []); expect($currentReview) ->not->toBeNull() ->and($progress)->toHaveCount(2) ->and($progress->pluck('key')->all())->toBe(['findings_with_outcome', 'review_completion']) ->and($progress->pluck('key')->contains('evidence_attachment'))->toBeFalse() ->and($progress->firstWhere('key', 'findings_with_outcome')['valueLabel'] ?? null)->toBe('2/3 (67%)') ->and($progress->firstWhere('key', 'review_completion')['valueLabel'] ?? null)->toBe($reviewCompletionLabel); }); it('renders current-review progress bars with a fixed visible track height and filament tone colors', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); mockTenantDashboardReadinessPermissions(); Finding::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => Finding::STATUS_RESOLVED, 'resolved_reason' => Finding::RESOLVE_REASON_REMEDIATED, 'resolved_at' => now(), ]); Finding::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => Finding::STATUS_RISK_ACCEPTED, ]); $snapshot = seedTenantReviewEvidence($tenant, findingCount: 1, driftCount: 0); composeTenantReviewForTest($tenant, $user, $snapshot); $this->actingAs($user); setTenantPanelContext($tenant); $content = $this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant)) ->assertSuccessful() ->getContent(); expect(substr_count($content, 'role="progressbar"'))->toBeGreaterThanOrEqual(2) ->and($content)->toContain('style="height: 0.5rem;"') ->and($content)->toContain('background-color: var(--primary-500);') ->and($content)->toContain('background-color: var(--warning-500);'); }); it('omits current-review progress bars when the review summary has no real denominators', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); mockTenantDashboardReadinessPermissions(); $snapshot = seedTenantReviewEvidence($tenant); $review = composeTenantReviewForTest($tenant, $user, $snapshot); $review->forceFill([ 'summary' => [], ])->save(); $summary = app(TenantDashboardSummaryBuilder::class) ->build($tenant, $user) ->toArray(); $currentReview = collect($summary['readinessCards'])->firstWhere('key', 'current_review'); expect($currentReview) ->not->toBeNull() ->and($currentReview['progress'] ?? null)->toBe([]); }); it('shows honest fallback states when review and evidence artifacts are not available yet', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); mockTenantDashboardReadinessPermissions(); $summary = app(TenantDashboardSummaryBuilder::class) ->build($tenant, $user) ->toArray(); $currentReview = collect($summary['readinessCards'])->firstWhere('key', 'current_review'); $providerHealth = collect($summary['readinessCards'])->firstWhere('key', 'provider_health'); $outputCard = collect($summary['readinessCards'])->firstWhere('key', 'customer_safe_output'); $evidenceCoverage = collect($summary['governanceStatus'])->firstWhere('label', 'Evidence coverage'); expect($currentReview) ->not->toBeNull() ->and($currentReview['status'])->toBe('No active review') ->and($currentReview['body'])->toBe('There is currently no review in progress for this tenant.') ->and($currentReview['actionUrl'])->toBe(TenantReviewResource::tenantScopedUrl('index', tenant: $tenant)) ->and($providerHealth) ->not->toBeNull() ->and($providerHealth['status'])->toBe('Provider status unavailable') ->and($providerHealth['body'])->toBe('No provider health snapshot is currently available for this tenant.') ->and($providerHealth['actionUrl'])->toBe(RequiredPermissionsLinks::requiredPermissions($tenant)) ->and($outputCard) ->not->toBeNull() ->and($outputCard['status'])->toBe('No customer-safe output available') ->and($outputCard['body'])->toBe('Generate a review pack once review and evidence are ready for handoff.') ->and($outputCard['actionLabel'])->toBe('View export artifacts') ->and($outputCard['actionUrl'])->toBe(CustomerReviewWorkspace::tenantPrefilterUrl($tenant)) ->and($evidenceCoverage) ->not->toBeNull() ->and($evidenceCoverage['value'])->toBe('Unavailable') ->and($evidenceCoverage['description'])->toBe('No evidence snapshot is currently available for customer-safe output.') ->and($evidenceCoverage['actionUrl'])->toBe(EvidenceSnapshotResource::getUrl('index', panel: 'tenant', tenant: $tenant)); });