PortfolioArrivalContextToken::SOURCE_TENANT_REGISTRY, 'tenantRouteKey' => (string) $tenant->external_id, 'workspaceId' => (int) $tenant->workspace_id, 'concernFamily' => PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH, 'concernState' => TenantBackupHealthAssessment::POSTURE_STALE, 'concernReason' => TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE, 'returnFilters' => [ 'backup_posture' => [TenantBackupHealthAssessment::POSTURE_STALE], ], ]; } function triageReviewDashboardWidget(User $user, Tenant $tenant, array $state): mixed { test()->actingAs($user); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); setTenantPanelContext($tenant); request()->attributes->remove('portfolio_triage.arrival_context'); return Livewire::withQueryParams([ PortfolioArrivalContextToken::QUERY_PARAMETER => PortfolioArrivalContextToken::encode($state), ])->actingAs($user)->test(TenantTriageArrivalContinuity::class); } it('returns 404 for non-members on the tenant dashboard triage route', function (): void { $tenant = Tenant::factory()->create(['status' => 'active']); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $this->seedPortfolioBackupConcern($tenant, TenantBackupHealthAssessment::POSTURE_STALE); $foreignTenant = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $tenant->workspace_id, ]); $this->seedPortfolioBackupConcern($foreignTenant, TenantBackupHealthAssessment::POSTURE_STALE); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(TenantDashboard::getUrl([ PortfolioArrivalContextToken::QUERY_PARAMETER => PortfolioArrivalContextToken::encode(triageReviewArrivalState($foreignTenant)), ], panel: 'tenant', tenant: $foreignTenant)) ->assertNotFound(); }); it('shows review actions as disabled for readonly members and still rejects a bypassed mutation with 403', function (): void { $tenant = Tenant::factory()->create(['status' => 'active']); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly'); $this->seedPortfolioBackupConcern($tenant, TenantBackupHealthAssessment::POSTURE_STALE); $component = triageReviewDashboardWidget($user, $tenant, triageReviewArrivalState($tenant)) ->assertActionVisible('markReviewed') ->assertActionDisabled('markReviewed'); $instance = $component->instance(); $componentReflection = new ReflectionObject($instance); $cachedActionsProperty = $componentReflection->getProperty('cachedActions'); $cachedActionsProperty->setAccessible(true); $cachedActions = $cachedActionsProperty->getValue($instance); $action = $cachedActions['markReviewed'] ?? null; expect($action)->not->toBeNull(); $actionReflection = new ReflectionObject($action); $disabledProperty = $actionReflection->getProperty('isDisabled'); $disabledProperty->setAccessible(true); $disabledProperty->setValue($action, false); $cachedActionsProperty->setValue($instance, $cachedActions); $instance->mountAction('markReviewed'); $mountedAction = $instance->getMountedAction(); expect($mountedAction)->not->toBeNull(); $mountedReflection = new ReflectionObject($mountedAction); $mountedDisabledProperty = $mountedReflection->getProperty('isDisabled'); $mountedDisabledProperty->setAccessible(true); $mountedDisabledProperty->setValue($mountedAction, false); try { $instance->callMountedAction(); $this->fail('Expected a 403 when bypassing the disabled action.'); } catch (HttpException $exception) { expect($exception->getStatusCode())->toBe(403); } expect(TenantTriageReview::query()->count())->toBe(0); }); it('writes review progress and audit state only after the preview-confirmed action executes', function (): void { [$user, $tenant] = $this->makePortfolioTriageActor('Authorization Success Tenant'); $this->seedPortfolioBackupConcern($tenant, TenantBackupHealthAssessment::POSTURE_STALE); $component = triageReviewDashboardWidget($user, $tenant, triageReviewArrivalState($tenant)) ->assertActionExists('markFollowUpNeeded', fn (Action $action): bool => $action->isConfirmationRequired() && str_contains((string) $action->getModalDescription(), 'Target state: Follow-up needed') && str_contains((string) $action->getModalDescription(), 'TenantPilot only')) ->mountAction('markFollowUpNeeded'); expect(TenantTriageReview::query()->count())->toBe(0); $component ->callMountedAction(); expect(TenantTriageReview::query() ->where('tenant_id', (int) $tenant->getKey()) ->where('current_state', TenantTriageReview::STATE_FOLLOW_UP_NEEDED) ->whereNull('resolved_at') ->exists())->toBeTrue() ->and(AuditLog::query() ->where('workspace_id', (int) $tenant->workspace_id) ->where('tenant_id', (int) $tenant->getKey()) ->where('action', AuditActionId::TenantTriageReviewMarkedFollowUpNeeded->value) ->exists())->toBeTrue(); Filament::setTenant(null, true); });