makePortfolioTriageActor('Anchor Backup Tenant'); $reviewedTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Reviewed Backup Tenant'); $followUpTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Follow-up Backup Tenant'); $changedTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Changed Backup Tenant'); $notReviewedTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Not Reviewed Backup Tenant'); $calmTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Calm Backup Tenant'); foreach ([$reviewedTenant, $followUpTenant, $changedTenant, $notReviewedTenant] as $tenant) { $this->seedPortfolioBackupConcern($tenant, TenantBackupHealthAssessment::POSTURE_STALE); } $this->seedPortfolioBackupConcern($calmTenant, TenantBackupHealthAssessment::POSTURE_HEALTHY); $this->seedPortfolioTriageReview($reviewedTenant, PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH, TenantTriageReview::STATE_REVIEWED, $user); $this->seedPortfolioTriageReview($followUpTenant, PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH, TenantTriageReview::STATE_FOLLOW_UP_NEEDED, $user); $this->seedPortfolioTriageReview($changedTenant, PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH, TenantTriageReview::STATE_REVIEWED, $user, changedFingerprint: true); $this->portfolioTriageRegistryList($user, $anchorTenant, [ 'backup_posture' => [TenantBackupHealthAssessment::POSTURE_STALE], ]) ->assertTableColumnExists('review_state') ->assertTableColumnFormattedStateSet('review_state', 'Reviewed', $reviewedTenant) ->assertTableColumnFormattedStateSet('review_state', 'Follow-up needed', $followUpTenant) ->assertTableColumnFormattedStateSet('review_state', 'Changed since review', $changedTenant) ->assertTableColumnFormattedStateSet('review_state', 'Not reviewed', $notReviewedTenant) ->assertDontSee('Calm Backup Tenant'); $reviewedNames = $this->portfolioTriageRegistryList($user, $anchorTenant, [ 'backup_posture' => [TenantBackupHealthAssessment::POSTURE_STALE], ]) ->filterTable('review_state', [TenantTriageReview::STATE_REVIEWED]) ->instance() ->getFilteredTableQuery() ?->pluck('tenants.name') ->all(); $followUpNames = $this->portfolioTriageRegistryList($user, $anchorTenant, [ 'backup_posture' => [TenantBackupHealthAssessment::POSTURE_STALE], ]) ->filterTable('review_state', [TenantTriageReview::STATE_FOLLOW_UP_NEEDED]) ->instance() ->getFilteredTableQuery() ?->pluck('tenants.name') ->all(); $changedNames = $this->portfolioTriageRegistryList($user, $anchorTenant, [ 'backup_posture' => [TenantBackupHealthAssessment::POSTURE_STALE], ]) ->filterTable('review_state', [TenantTriageReview::DERIVED_STATE_CHANGED_SINCE_REVIEW]) ->instance() ->getFilteredTableQuery() ?->pluck('tenants.name') ->all(); $notReviewedNames = $this->portfolioTriageRegistryList($user, $anchorTenant, [ 'backup_posture' => [TenantBackupHealthAssessment::POSTURE_STALE], ]) ->filterTable('review_state', [TenantTriageReview::DERIVED_STATE_NOT_REVIEWED]) ->instance() ->getFilteredTableQuery() ?->pluck('tenants.name') ->all(); expect($reviewedNames)->toBe(['Reviewed Backup Tenant']) ->and($followUpNames)->toBe(['Follow-up Backup Tenant']) ->and($changedNames)->toBe(['Changed Backup Tenant']) ->and($notReviewedNames)->toBe(['Not Reviewed Backup Tenant']); }); it('uses the highest-priority current concern family when the registry slice is mixed', function (): void { [$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor Mixed Tenant'); $mixedTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Mixed Concern Tenant'); $backupSet = $this->seedPortfolioBackupConcern($mixedTenant, TenantBackupHealthAssessment::POSTURE_STALE); $this->seedPortfolioRecoveryConcern($mixedTenant, 'failed', $backupSet); $this->seedPortfolioTriageReview( $mixedTenant, PortfolioArrivalContextToken::FAMILY_RECOVERY_EVIDENCE, TenantTriageReview::STATE_FOLLOW_UP_NEEDED, $user, ); $this->portfolioTriageRegistryList($user, $anchorTenant, [ 'triage_sort' => TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST, ]) ->assertTableColumnFormattedStateSet('review_state', 'Follow-up needed', $mixedTenant) ->assertSee('Recovery evidence'); }); it('keeps review-state mutations in overflow with a preview-confirmed write path', function (): void { [$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor Action Tenant'); $actionTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Action Backup Tenant'); $this->seedPortfolioBackupConcern($actionTenant, TenantBackupHealthAssessment::POSTURE_STALE); $component = $this->portfolioTriageRegistryList($user, $anchorTenant, [ 'backup_posture' => [TenantBackupHealthAssessment::POSTURE_STALE], ]) ->assertTableActionVisible('openTenant', $actionTenant) ->assertTableActionEnabled('markReviewed', $actionTenant) ->assertTableActionExists('markReviewed', fn (Action $action): bool => $action->isConfirmationRequired() && str_contains((string) $action->getModalDescription(), 'Concern family: Backup health') && str_contains((string) $action->getModalDescription(), 'Target state: Reviewed') && str_contains((string) $action->getModalDescription(), 'TenantPilot only'), $actionTenant) ->assertTableActionExists('markFollowUpNeeded', fn (Action $action): bool => $action->isConfirmationRequired(), $actionTenant); $action = $component->instance()->getAction([ [ 'name' => 'markReviewed', 'context' => [ 'table' => true, 'recordKey' => (string) $actionTenant->getKey(), ], ], ]); expect(app(CapabilityResolver::class)->can($user, $actionTenant, Capabilities::TENANT_TRIAGE_REVIEW_MANAGE))->toBeTrue(); expect($action)->not->toBeNull() ->and($action?->getRecord())->toBeInstanceOf(Tenant::class) ->and((int) $action?->getRecord()?->getKey())->toBe((int) $actionTenant->getKey()) ->and($action?->isDisabled())->toBeFalse() ->and($component->instance()->mountedActionShouldOpenModal($action))->toBeTrue(); $component->mountAction([ [ 'name' => 'markReviewed', 'context' => [ 'table' => true, 'recordKey' => (string) $actionTenant->getKey(), ], ], ]); $mountedAction = $component->instance()->getMountedAction(); expect($mountedAction)->not->toBeNull() ->and($mountedAction?->getRecord())->toBeInstanceOf(Tenant::class) ->and((int) $mountedAction?->getRecord()?->getKey())->toBe((int) $actionTenant->getKey()); $component ->callMountedAction(); expect(TenantTriageReview::query() ->where('tenant_id', (int) $actionTenant->getKey()) ->where('concern_family', PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH) ->where('current_state', TenantTriageReview::STATE_REVIEWED) ->whereNull('resolved_at') ->exists())->toBeTrue() ->and($component->instance())->toBeInstanceOf(ListTenants::class); });