TenantAtlas/apps/platform/tests/Feature/Concerns/BuildsPortfolioTriageFixtures.php
ahmido 28e62bd22c feat: preserve portfolio triage arrival context (#218)
## Summary
- preserve portfolio triage arrival context from workspace overview and tenant registry drill-throughs
- add a tenant dashboard continuity widget plus bounded arrival token and resolver support
- add focused Pest coverage for arrival routing, return flow, RBAC degradation, and request-local performance
- include the Spec 187 spec, plan, research, data model, quickstart, contract, and tasks artifacts

## Validation
- integrated browser smoke: workspace overview -> tenant dashboard arrival -> backup sets CTA
- integrated browser smoke: tenant registry triage -> tenant dashboard arrival -> return to tenant triage
- branch includes focused automated test coverage for the new arrival-context surfaces

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #218
2026-04-09 21:38:31 +00:00

166 lines
5.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Concerns;
use App\Filament\Resources\TenantResource\Pages\ListTenants;
use App\Models\BackupSet;
use App\Models\RestoreRun;
use App\Models\Tenant;
use App\Models\User;
use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\RestoreSafety\RestoreResultAttention;
use App\Support\Tenants\TenantRecoveryTriagePresentation;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Livewire\Livewire;
trait BuildsPortfolioTriageFixtures
{
/**
* @return array{0: User, 1: Tenant}
*/
protected function makePortfolioTriageActor(
string $tenantName = 'Anchor Tenant',
string $role = 'owner',
string $workspaceRole = 'readonly',
): array {
$tenant = Tenant::factory()->create([
'status' => 'active',
'name' => $tenantName,
]);
[$user, $tenant] = createUserWithTenant(
tenant: $tenant,
role: $role,
workspaceRole: $workspaceRole,
);
workspaceOverviewSeedQuietTenantTruth($tenant);
return [$user, $tenant];
}
protected function makePortfolioTriagePeer(User $user, Tenant $workspaceTenant, string $name): Tenant
{
$tenant = Tenant::factory()->create([
'status' => 'active',
'workspace_id' => (int) $workspaceTenant->workspace_id,
'name' => $name,
]);
createUserWithTenant(
tenant: $tenant,
user: $user,
role: 'owner',
workspaceRole: 'readonly',
);
workspaceOverviewSeedQuietTenantTruth($tenant);
return $tenant;
}
protected function seedPortfolioBackupConcern(Tenant $tenant, string $state): ?BackupSet
{
return match ($state) {
TenantBackupHealthAssessment::POSTURE_ABSENT => null,
TenantBackupHealthAssessment::POSTURE_STALE => BackupSet::factory()
->for($tenant)
->staleCompleted()
->create([
'name' => 'Portfolio stale backup',
]),
TenantBackupHealthAssessment::POSTURE_DEGRADED => BackupSet::factory()
->for($tenant)
->degradedCompleted()
->create([
'name' => 'Portfolio degraded backup',
]),
default => workspaceOverviewSeedHealthyBackup($tenant, [
'name' => 'Portfolio healthy backup',
]),
};
}
protected function seedPortfolioRecoveryConcern(
Tenant $tenant,
string $reason = 'no_history',
?BackupSet $backupSet = null,
): ?RestoreRun {
$backupSet ??= workspaceOverviewSeedHealthyBackup($tenant, [
'name' => 'Portfolio recovery backup',
]);
return match ($reason) {
'no_history' => null,
RestoreResultAttention::STATE_FAILED => RestoreRun::factory()
->for($tenant)
->for($backupSet)
->failedOutcome()
->create([
'completed_at' => now()->subMinutes(10),
]),
RestoreResultAttention::STATE_PARTIAL => RestoreRun::factory()
->for($tenant)
->for($backupSet)
->partialOutcome()
->create([
'completed_at' => now()->subMinutes(10),
]),
RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP => RestoreRun::factory()
->for($tenant)
->for($backupSet)
->completedWithFollowUp()
->create([
'completed_at' => now()->subMinutes(10),
]),
default => RestoreRun::factory()
->for($tenant)
->for($backupSet)
->completedOutcome()
->create([
'completed_at' => now()->subMinutes(10),
]),
};
}
protected function usePortfolioTriageWorkspace(User $user, Tenant $tenant): void
{
test()->actingAs($user);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Filament::setTenant(null, true);
request()->attributes->remove('tenant_resource.posture_snapshot');
session()->forget('tables.'.md5(ListTenants::class).'_filters');
session()->forget('tables.'.md5(ListTenants::class).'_search');
session()->forget('tables.'.md5(ListTenants::class).'_sort');
}
protected function portfolioTriageRegistryList(User $user, Tenant $workspaceTenant, array $query = []): mixed
{
$this->usePortfolioTriageWorkspace($user, $workspaceTenant);
$factory = $query !== []
? Livewire::withQueryParams($query)->actingAs($user)
: Livewire::actingAs($user);
return $factory->test(ListTenants::class);
}
/**
* @return array{backup_posture: list<string>, recovery_evidence: list<string>, triage_sort: string|null}
*/
protected function portfolioReturnFilters(
array $backupPosture = [],
array $recoveryEvidence = [],
?string $triageSort = TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST,
): array {
return [
'backup_posture' => $backupPosture,
'recovery_evidence' => $recoveryEvidence,
'triage_sort' => $triageSort,
];
}
}