TenantAtlas/apps/platform/tests/Feature/Dashboard/TenantDashboardProductizationReadinessTest.php
ahmido 3aeb0d04b8 Auto: 266-tenant-dashboard-productization-v1 → platform-dev (#322)
Automated PR created by Copilot per user request. Branch pushed: 266-tenant-dashboard-productization-v1

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #322
2026-05-03 14:03:46 +00:00

336 lines
14 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Pages\TenantDashboard;
use App\Filament\Resources\EvidenceSnapshotResource;
use App\Filament\Resources\ReviewPackResource;
use App\Filament\Resources\TenantReviewResource;
use App\Filament\Widgets\Dashboard\RecoveryReadiness;
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\Finding;
use App\Models\ProviderConnection;
use App\Models\ReviewPack;
use App\Services\Intune\TenantRequiredPermissionsViewModelBuilder;
use App\Support\Links\RequiredPermissionsLinks;
use App\Support\TenantDashboard\TenantDashboardSummaryBuilder;
use Filament\Facades\Filament;
use Livewire\Livewire;
use function Pest\Laravel\mock;
function mockTenantDashboardReadinessPermissions(array $overview = []): void
{
mock(TenantRequiredPermissionsViewModelBuilder::class, function ($mock) use ($overview): void {
$mock->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));
});