PortfolioArrivalContextToken::encode($state), ], panel: 'tenant', tenant: $tenant); } it('renders source surface, concern, next step, and return flow for workspace backup arrivals', function (): void { [$user, $tenant] = $this->makePortfolioTriageActor('Workspace Backup Tenant'); $this->actingAs($user); $arrivalUrl = tenantDashboardArrivalUrl($tenant, [ 'sourceSurface' => PortfolioArrivalContextToken::SOURCE_WORKSPACE_OVERVIEW, 'tenantRouteKey' => (string) $tenant->external_id, 'workspaceId' => (int) $tenant->workspace_id, 'concernFamily' => PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH, 'concernState' => TenantBackupHealthAssessment::POSTURE_ABSENT, 'concernReason' => TenantBackupHealthAssessment::REASON_NO_BACKUP_BASIS, ]); $this->get($arrivalUrl) ->assertOk() ->assertSee('Triage arrival') ->assertSee('Workspace overview triage') ->assertSee('Backup health') ->assertSee('Absent') ->assertSee('Opened from workspace overview triage because no usable backup basis was visible.') ->assertSee('Open backup sets') ->assertSee('Return to workspace overview') ->assertSee(BackupSetResource::getUrl('index', [ 'backup_health_reason' => TenantBackupHealthAssessment::REASON_NO_BACKUP_BASIS, ], panel: 'tenant', tenant: $tenant), false) ->assertSee(route('admin.home'), false); }); it('renders registry arrival continuity with restore follow-up and preserved return filters', function (): void { [$user, $tenant] = $this->makePortfolioTriageActor('Registry Recovery Tenant'); $restoreRun = $this->seedPortfolioRecoveryConcern($tenant, RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP); $this->actingAs($user); $returnUrl = TenantResource::getUrl('index', [ 'recovery_evidence' => [TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED], 'triage_sort' => TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST, ], panel: 'admin'); $arrivalUrl = tenantDashboardArrivalUrl($tenant, [ 'sourceSurface' => PortfolioArrivalContextToken::SOURCE_TENANT_REGISTRY, 'tenantRouteKey' => (string) $tenant->external_id, 'workspaceId' => (int) $tenant->workspace_id, 'concernFamily' => PortfolioArrivalContextToken::FAMILY_RECOVERY_EVIDENCE, 'concernState' => TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED, 'concernReason' => RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP, 'returnFilters' => [ 'recovery_evidence' => [TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED], 'triage_sort' => TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST, ], ]); $this->get($arrivalUrl) ->assertOk() ->assertSee('Tenant registry triage') ->assertSee('Recovery evidence') ->assertSee('Weakened') ->assertSee('Open restore run') ->assertSee('Return to tenant triage') ->assertSee(RestoreRunResource::getUrl('view', [ 'record' => (int) $restoreRun?->getKey(), 'recovery_posture_reason' => RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP, ], panel: 'tenant', tenant: $tenant), false) ->assertSee($returnUrl, false); }); it('suppresses the continuity block for generic or malformed sessions', function (): void { [$user, $tenant] = $this->makePortfolioTriageActor('Generic Tenant Session'); $this->actingAs($user); $this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant)) ->assertOk() ->assertDontSee('Triage arrival'); $this->get(TenantDashboard::getUrl([ PortfolioArrivalContextToken::QUERY_PARAMETER => 'not-base64url', ], panel: 'tenant', tenant: $tenant)) ->assertOk() ->assertDontSee('Triage arrival'); }); it('keeps the continuity block truthful when current truth has changed and multiple concerns remain visible', function (): void { [$user, $tenant] = $this->makePortfolioTriageActor('Truth Shift Tenant'); $backupSet = $this->seedPortfolioBackupConcern($tenant, TenantBackupHealthAssessment::POSTURE_STALE); $this->seedPortfolioRecoveryConcern($tenant, RestoreResultAttention::STATE_PARTIAL, $backupSet); $this->actingAs($user); $arrivalUrl = tenantDashboardArrivalUrl($tenant, [ 'sourceSurface' => PortfolioArrivalContextToken::SOURCE_TENANT_REGISTRY, 'tenantRouteKey' => (string) $tenant->external_id, 'workspaceId' => (int) $tenant->workspace_id, 'concernFamily' => PortfolioArrivalContextToken::FAMILY_RECOVERY_EVIDENCE, 'concernState' => TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_UNVALIDATED, 'concernReason' => 'no_history', 'returnFilters' => [ 'triage_sort' => TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST, ], ]); $this->get($arrivalUrl) ->assertOk() ->assertSee('Current recovery evidence now looks Weakened.') ->assertSee('Backup posture also still needs follow-up.') ->assertSee('Open restore run'); }); it('degrades the next-step CTA when the operator cannot open deeper follow-up surfaces', function (): void { [$user, $tenant] = $this->makePortfolioTriageActor('Restricted Arrival Tenant'); $this->actingAs($user); mock(CapabilityResolver::class, function ($mock) use ($tenant): void { $mock->shouldReceive('isMember') ->andReturnUsing(static fn ($user, $resolvedTenant): bool => (int) $resolvedTenant->getKey() === (int) $tenant->getKey()); $mock->shouldReceive('can') ->andReturnUsing(static function ($user, $resolvedTenant, string $capability) use ($tenant): bool { expect((int) $resolvedTenant->getKey())->toBe((int) $tenant->getKey()); return $capability !== Capabilities::TENANT_VIEW; }); }); $arrivalUrl = tenantDashboardArrivalUrl($tenant, [ 'sourceSurface' => PortfolioArrivalContextToken::SOURCE_WORKSPACE_OVERVIEW, 'tenantRouteKey' => (string) $tenant->external_id, 'workspaceId' => (int) $tenant->workspace_id, 'concernFamily' => PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH, 'concernState' => TenantBackupHealthAssessment::POSTURE_ABSENT, 'concernReason' => TenantBackupHealthAssessment::REASON_NO_BACKUP_BASIS, ]); $this->get($arrivalUrl) ->assertOk() ->assertSee('Triage arrival') ->assertSee(UiTooltips::INSUFFICIENT_PERMISSION) ->assertDontSee(BackupSetResource::getUrl('index', [ 'backup_health_reason' => TenantBackupHealthAssessment::REASON_NO_BACKUP_BASIS, ], panel: 'tenant', tenant: $tenant), false); });