## 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
98 lines
4.3 KiB
PHP
98 lines
4.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\TenantDashboard;
|
|
use App\Filament\Resources\RestoreRunResource;
|
|
use App\Models\Tenant;
|
|
use App\Services\Auth\CapabilityResolver;
|
|
use App\Support\Auth\Capabilities;
|
|
use App\Support\PortfolioTriage\PortfolioArrivalContextToken;
|
|
use App\Support\Rbac\UiTooltips;
|
|
use App\Support\RestoreSafety\RestoreResultAttention;
|
|
use App\Support\Tenants\TenantRecoveryTriagePresentation;
|
|
use Tests\Feature\Concerns\BuildsPortfolioTriageFixtures;
|
|
|
|
use function Pest\Laravel\mock;
|
|
|
|
uses(BuildsPortfolioTriageFixtures::class);
|
|
|
|
function tenantDashboardVisibilityArrivalUrl(\App\Models\Tenant $tenant): string
|
|
{
|
|
return TenantDashboard::getUrl([
|
|
PortfolioArrivalContextToken::QUERY_PARAMETER => 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();
|
|
});
|