for($tenant)->recentCompleted()->create([ 'name' => 'Recovery visibility backup', 'item_count' => 1, ]); BackupItem::factory()->for($tenant)->for($backupSet)->create([ 'payload' => ['id' => 'recovery-visibility-policy'], 'metadata' => [], 'assignments' => [], ]); return RestoreRun::factory() ->for($tenant) ->for($backupSet) ->failedOutcome() ->create([ 'completed_at' => now()->subMinutes(10), ]); } it('keeps recovery drillthrough inspectable for readonly tenant members', function (): void { $tenant = Tenant::factory()->create(); [$user] = createUserWithTenant($tenant, role: 'readonly'); $restoreRun = seedRecoveryVisibilityScenario($tenant); $this->actingAs($user); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); Livewire::test(NeedsAttention::class) ->assertSee('Recent restore failed') ->assertSee('Open restore run'); Livewire::test(RecoveryReadiness::class) ->assertSee('Recovery evidence') ->assertSee('Weakened'); $this->get(TenantDashboard::getUrl(tenant: $tenant)) ->assertOk(); $this->get(RestoreRunResource::getUrl('index', tenant: $tenant)) ->assertOk(); $this->get(RestoreRunResource::getUrl('view', [ 'record' => (int) $restoreRun->getKey(), ], panel: 'tenant', tenant: $tenant)) ->assertOk(); }); it('keeps recovery summaries cautious while restore-history drillthrough is forbidden for in-scope members without view capability', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $restoreRun = seedRecoveryVisibilityScenario($tenant); $this->actingAs($user); Gate::define(Capabilities::TENANT_VIEW, fn (): bool => false); mock(CapabilityResolver::class, function ($mock) use ($tenant): void { $mock->shouldReceive('primeMemberships')->andReturnNull(); $mock->shouldReceive('isMember') ->andReturnUsing(static fn ($user, Tenant $resolvedTenant): bool => (int) $resolvedTenant->getKey() === (int) $tenant->getKey()); $mock->shouldReceive('can') ->andReturnUsing(static function ($user, Tenant $resolvedTenant, string $capability) use ($tenant): bool { expect((int) $resolvedTenant->getKey())->toBe((int) $tenant->getKey()); return match ($capability) { Capabilities::TENANT_VIEW => false, default => true, }; }); }); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); Livewire::test(NeedsAttention::class) ->assertSee('Recent restore failed') ->assertSee(UiTooltips::INSUFFICIENT_PERMISSION) ->assertSee('Open restore run'); Livewire::test(RecoveryReadiness::class) ->assertSee('Recovery evidence') ->assertSee('Weakened') ->assertSee(UiTooltips::INSUFFICIENT_PERMISSION); $this->get(RestoreRunResource::getUrl('index', tenant: $tenant)) ->assertForbidden(); $this->get(RestoreRunResource::getUrl('view', [ 'record' => (int) $restoreRun->getKey(), ], panel: 'tenant', tenant: $tenant)) ->assertForbidden(); }); it('keeps dashboard and restore-history recovery surfaces deny-as-not-found for non-members', function (): void { $tenant = Tenant::factory()->create(); $otherTenant = Tenant::factory()->create(); [$user] = createUserWithTenant($otherTenant, role: 'owner'); $restoreRun = seedRecoveryVisibilityScenario($tenant); $this->actingAs($user); $this->get(TenantDashboard::getUrl(tenant: $tenant)) ->assertNotFound(); $this->get(RestoreRunResource::getUrl('index', tenant: $tenant)) ->assertNotFound(); $this->get(RestoreRunResource::getUrl('view', [ 'record' => (int) $restoreRun->getKey(), ], panel: 'tenant', tenant: $tenant)) ->assertNotFound(); });