PortfolioArrivalContextToken::encode([ '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_FAILED, 'returnFilters' => [ 'recovery_evidence' => [TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED], 'triage_sort' => TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST, ], ]), ], panel: 'tenant', tenant: $tenant); } it('shows an actionable follow-up link for in-scope members who can open the target surface', function (): void { [$user, $tenant] = $this->makePortfolioTriageActor('Visible Arrival Tenant', role: 'readonly'); $restoreRun = $this->seedPortfolioRecoveryConcern($tenant, RestoreResultAttention::STATE_FAILED); $this->actingAs($user); $this->get(tenantDashboardVisibilityArrivalUrl($tenant)) ->assertOk() ->assertSee('Triage arrival') ->assertSee('Open restore run') ->assertSee('Return to tenant triage'); $this->get(RestoreRunResource::getUrl('view', [ 'record' => (int) $restoreRun->getKey(), 'recovery_posture_reason' => RestoreResultAttention::STATE_FAILED, ], panel: 'tenant', tenant: $tenant)) ->assertOk(); }); it('keeps the arrival block truthful while degrading the CTA for in-scope members without follow-up capability', function (): void { [$user, $tenant] = $this->makePortfolioTriageActor('Restricted Arrival Tenant'); $restoreRun = $this->seedPortfolioRecoveryConcern($tenant, RestoreResultAttention::STATE_FAILED); $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; }); }); $this->get(tenantDashboardVisibilityArrivalUrl($tenant)) ->assertOk() ->assertSee('Triage arrival') ->assertSee(UiTooltips::INSUFFICIENT_PERMISSION) ->assertDontSee('href="'.e(RestoreRunResource::getUrl('view', [ 'record' => (int) $restoreRun->getKey(), 'recovery_posture_reason' => RestoreResultAttention::STATE_FAILED, ], panel: 'tenant', tenant: $tenant)).'"', false); $this->get(RestoreRunResource::getUrl('view', [ 'record' => (int) $restoreRun->getKey(), 'recovery_posture_reason' => RestoreResultAttention::STATE_FAILED, ], panel: 'tenant', tenant: $tenant)) ->assertForbidden(); }); it('keeps tenant-dashboard arrival routes deny-as-not-found for non-members', function (): void { $tenant = Tenant::factory()->create(); [$user] = $this->makePortfolioTriageActor('Other Tenant'); $this->seedPortfolioRecoveryConcern($tenant, RestoreResultAttention::STATE_FAILED); $this->actingAs($user); $this->get(tenantDashboardVisibilityArrivalUrl($tenant)) ->assertNotFound(); });