TenantAtlas/apps/platform/tests/Feature/Filament/WorkspaceOverviewSummaryMetricsTest.php
ahmido e64bae9cfc feat: cut over tenant core to managed environments (#335)
## Summary
- replace the legacy Tenant and TenantMembership core models with ManagedEnvironment and ManagedEnvironmentMembership
- propagate the managed environment naming and key changes across Filament resources, pages, controllers, jobs, models, and supporting runtime paths
- add feature 279 spec artifacts and focused managed-environment test coverage for model behavior, route binding, panel context, authorization, and legacy guardrails

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ManagedEnvironment/LegacyTenantCoreGuardTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentAuthorizationTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentPanelContextTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentRouteBindingTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentContextResolverTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentModelTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`

## Notes
- branch pushed from commit `1123b122`
- browser smoke test file was added but not run in this pass

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #335
2026-05-07 06:38:14 +00:00

245 lines
12 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\AlertDelivery;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Support\Workspaces\WorkspaceOverviewBuilder;
use Carbon\CarbonImmutable;
afterEach(function (): void {
CarbonImmutable::setTestNow();
});
it('counts governance attention by affected tenant instead of raw issue totals', function (): void {
$tenantOverdue = ManagedEnvironment::factory()->create(['status' => 'active']);
[$user, $tenantOverdue] = createUserWithTenant($tenantOverdue, role: 'owner', workspaceRole: 'readonly');
[$overdueProfile, $overdueSnapshot] = seedActiveBaselineForTenant($tenantOverdue);
seedBaselineCompareRun($tenantOverdue, $overdueProfile, $overdueSnapshot, workspaceOverviewCompareCoverage());
Finding::factory()->count(3)->for($tenantOverdue)->create([
'workspace_id' => (int) $tenantOverdue->workspace_id,
'status' => Finding::STATUS_TRIAGED,
'due_at' => now()->subDay(),
]);
$tenantExpiring = ManagedEnvironment::factory()->create([
'status' => 'active',
'workspace_id' => (int) $tenantOverdue->workspace_id,
'name' => 'Expiring ManagedEnvironment',
]);
createUserWithTenant($tenantExpiring, $user, role: 'owner', workspaceRole: 'readonly');
[$expiringProfile, $expiringSnapshot] = seedActiveBaselineForTenant($tenantExpiring);
seedBaselineCompareRun($tenantExpiring, $expiringProfile, $expiringSnapshot, workspaceOverviewCompareCoverage());
$finding = Finding::factory()->riskAccepted()->create([
'workspace_id' => (int) $tenantExpiring->workspace_id,
'managed_environment_id' => (int) $tenantExpiring->getKey(),
]);
FindingException::query()->create([
'workspace_id' => (int) $tenantExpiring->workspace_id,
'managed_environment_id' => (int) $tenantExpiring->getKey(),
'finding_id' => (int) $finding->getKey(),
'requested_by_user_id' => (int) $user->getKey(),
'owner_user_id' => (int) $user->getKey(),
'approved_by_user_id' => (int) $user->getKey(),
'status' => FindingException::STATUS_EXPIRING,
'current_validity_state' => FindingException::VALIDITY_EXPIRING,
'request_reason' => 'Pending governance review',
'approval_reason' => 'Short lived exception',
'requested_at' => now()->subDays(2),
'approved_at' => now()->subDay(),
'effective_from' => now()->subDay(),
'expires_at' => now()->addDay(),
'review_due_at' => now()->addDay(),
'evidence_summary' => ['reference_count' => 0],
]);
$tenantStale = ManagedEnvironment::factory()->create([
'status' => 'active',
'workspace_id' => (int) $tenantOverdue->workspace_id,
'name' => 'Stale ManagedEnvironment',
]);
createUserWithTenant($tenantStale, $user, role: 'owner', workspaceRole: 'readonly');
[$staleProfile, $staleSnapshot] = seedActiveBaselineForTenant($tenantStale);
seedBaselineCompareRun(
$tenantStale,
$staleProfile,
$staleSnapshot,
workspaceOverviewCompareCoverage(),
completedAt: now()->subDays(10),
);
$tenantFailedCompare = ManagedEnvironment::factory()->create([
'status' => 'active',
'workspace_id' => (int) $tenantOverdue->workspace_id,
'name' => 'Failed Compare ManagedEnvironment',
]);
createUserWithTenant($tenantFailedCompare, $user, role: 'owner', workspaceRole: 'readonly');
[$failedProfile, $failedSnapshot] = seedActiveBaselineForTenant($tenantFailedCompare);
seedBaselineCompareRun(
$tenantFailedCompare,
$failedProfile,
$failedSnapshot,
workspaceOverviewCompareCoverage(),
outcome: \App\Support\OperationRunOutcome::Failed->value,
);
$workspace = $tenantOverdue->workspace()->firstOrFail();
$overview = app(WorkspaceOverviewBuilder::class)->build($workspace, $user);
$metrics = collect($overview['summary_metrics'])->keyBy('key');
expect($metrics->get('governance_attention_tenants')['value'])->toBe(4)
->and($metrics->get('governance_attention_tenants')['category'])->toBe('governance_risk')
->and($metrics->get('governance_attention_tenants')['destination']['kind'])->toBe('choose_tenant');
});
it('keeps activity and alerts metrics separate from governance risk', function (): void {
$tenant = ManagedEnvironment::factory()->create(['status' => 'active']);
[$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'readonly');
[$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
seedBaselineCompareRun($tenant, $profile, $snapshot, workspaceOverviewCompareCoverage());
OperationRun::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'status' => \App\Support\OperationRunStatus::Running->value,
'outcome' => \App\Support\OperationRunOutcome::Pending->value,
]);
AlertDelivery::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'status' => AlertDelivery::STATUS_FAILED,
'created_at' => now(),
]);
$workspace = $tenant->workspace()->firstOrFail();
$overview = app(WorkspaceOverviewBuilder::class)->build($workspace, $user);
$metrics = collect($overview['summary_metrics'])->keyBy('key');
expect($metrics->get('governance_attention_tenants')['value'])->toBe(0)
->and($metrics->get('active_operations')['value'])->toBe(1)
->and($metrics->get('active_operations')['category'])->toBe('activity')
->and($metrics->get('active_operations')['destination']['kind'])->toBe('operations_index')
->and($metrics->get('alert_failures')['value'])->toBe(1)
->and($metrics->get('alert_failures')['category'])->toBe('alerts');
});
it('counts backup and recovery attention tenants separately and chooses precise or fallback destinations', function (): void {
CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 9, 9, 0, 0, 'UTC'));
$recoveryTenantA = ManagedEnvironment::factory()->create([
'status' => 'active',
'name' => 'Recovery ManagedEnvironment A',
]);
[$user, $recoveryTenantA] = createUserWithTenant($recoveryTenantA, role: 'owner', workspaceRole: 'readonly');
workspaceOverviewSeedQuietTenantTruth($recoveryTenantA);
$recoveryTenantABackup = workspaceOverviewSeedHealthyBackup($recoveryTenantA, [
'completed_at' => now()->subMinutes(20),
]);
workspaceOverviewSeedRestoreHistory($recoveryTenantA, $recoveryTenantABackup, 'follow_up');
$recoveryTenantB = ManagedEnvironment::factory()->create([
'status' => 'active',
'workspace_id' => (int) $recoveryTenantA->workspace_id,
'name' => 'Recovery ManagedEnvironment B',
]);
createUserWithTenant($recoveryTenantB, $user, role: 'owner', workspaceRole: 'readonly');
workspaceOverviewSeedQuietTenantTruth($recoveryTenantB);
$recoveryTenantBBackup = workspaceOverviewSeedHealthyBackup($recoveryTenantB, [
'completed_at' => now()->subMinutes(22),
]);
workspaceOverviewSeedRestoreHistory($recoveryTenantB, $recoveryTenantBBackup, 'failed');
$backupTenantA = ManagedEnvironment::factory()->create([
'status' => 'active',
'workspace_id' => (int) $recoveryTenantA->workspace_id,
'name' => 'Backup ManagedEnvironment A',
]);
createUserWithTenant($backupTenantA, $user, role: 'owner', workspaceRole: 'readonly');
workspaceOverviewSeedQuietTenantTruth($backupTenantA);
$backupTenantABackup = workspaceOverviewSeedHealthyBackup($backupTenantA, [
'completed_at' => now()->subDays(2),
]);
workspaceOverviewSeedRestoreHistory($backupTenantA, $backupTenantABackup, 'completed');
$backupTenantB = ManagedEnvironment::factory()->create([
'status' => 'active',
'workspace_id' => (int) $recoveryTenantA->workspace_id,
'name' => 'Backup ManagedEnvironment B',
]);
createUserWithTenant($backupTenantB, $user, role: 'owner', workspaceRole: 'readonly');
workspaceOverviewSeedQuietTenantTruth($backupTenantB);
$backupTenantBBackup = workspaceOverviewSeedHealthyBackup($backupTenantB, [
'completed_at' => now()->subMinutes(18),
], [
'payload' => [],
'metadata' => [
'source' => 'metadata_only',
'assignments_fetch_failed' => true,
],
'assignments' => [],
]);
workspaceOverviewSeedRestoreHistory($backupTenantB, $backupTenantBBackup, 'completed');
$calmTenant = ManagedEnvironment::factory()->create([
'status' => 'active',
'workspace_id' => (int) $recoveryTenantA->workspace_id,
'name' => 'Calm ManagedEnvironment',
]);
createUserWithTenant($calmTenant, $user, role: 'owner', workspaceRole: 'readonly');
workspaceOverviewSeedQuietTenantTruth($calmTenant);
$calmBackup = workspaceOverviewSeedHealthyBackup($calmTenant, [
'completed_at' => now()->subMinutes(15),
]);
workspaceOverviewSeedRestoreHistory($calmTenant, $calmBackup, 'completed');
$workspace = $recoveryTenantA->workspace()->firstOrFail();
$overview = app(WorkspaceOverviewBuilder::class)->build($workspace, $user);
$metrics = collect($overview['summary_metrics'])->keyBy('key');
$backupDestination = $metrics->get('backup_attention_tenants')['destination'];
$recoveryDestination = $metrics->get('recovery_attention_tenants')['destination'];
expect($metrics->get('backup_attention_tenants')['value'])->toBe(2)
->and($metrics->get('backup_attention_tenants')['category'])->toBe('backup_health')
->and($metrics->get('backup_attention_tenants')['destination']['kind'])->toBe('choose_tenant')
->and($metrics->get('backup_attention_tenants')['destination_url'])->toStartWith(\App\Filament\Resources\TenantResource::getUrl(panel: 'admin'))
->and($backupDestination['filters'])->toBe([
'backup_posture' => ['absent', 'stale', 'degraded'],
'triage_sort' => 'worst_first',
])
->and($metrics->get('recovery_attention_tenants')['value'])->toBe(2)
->and($metrics->get('recovery_attention_tenants')['category'])->toBe('recovery_evidence')
->and($recoveryDestination['kind'])->toBe('choose_tenant')
->and($metrics->get('recovery_attention_tenants')['destination_url'])->toStartWith(\App\Filament\Resources\TenantResource::getUrl(panel: 'admin'))
->and($recoveryDestination['filters'])->toBe([
'recovery_evidence' => ['weakened', 'unvalidated'],
'triage_sort' => 'worst_first',
]);
});
it('keeps backup and recovery attention counts at zero for calm visible tenants', function (): void {
CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 9, 9, 0, 0, 'UTC'));
$tenant = ManagedEnvironment::factory()->create(['status' => 'active']);
[$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'readonly');
workspaceOverviewSeedQuietTenantTruth($tenant);
$backupSet = workspaceOverviewSeedHealthyBackup($tenant, [
'completed_at' => now()->subMinutes(10),
]);
workspaceOverviewSeedRestoreHistory($tenant, $backupSet, 'completed');
$workspace = $tenant->workspace()->firstOrFail();
$overview = app(WorkspaceOverviewBuilder::class)->build($workspace, $user);
$metrics = collect($overview['summary_metrics'])->keyBy('key');
expect($metrics->get('backup_attention_tenants')['value'])->toBe(0)
->and($metrics->get('recovery_attention_tenants')['value'])->toBe(0)
->and($overview['calmness']['checked_domains'])->toContain('backup_health', 'recovery_evidence');
});