TenantAtlas/tests/Feature/Filament/Alerts/AlertDeliveryViewerTest.php
2026-03-09 07:25:40 +01:00

234 lines
8.0 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Resources\AlertDeliveryResource;
use App\Filament\Resources\AlertDeliveryResource\Pages\ListAlertDeliveries;
use App\Filament\Resources\AlertRuleResource;
use App\Models\AlertDelivery;
use App\Models\AlertDestination;
use App\Models\AlertRule;
use App\Models\Tenant;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Services\Auth\WorkspaceCapabilityResolver;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Livewire\Features\SupportTesting\Testable;
use Livewire\Livewire;
function getAlertDeliveryEmptyStateAction(Testable $component, string $name): ?Action
{
foreach ($component->instance()->getTable()->getEmptyStateActions() as $action) {
if ($action instanceof Action && $action->getName() === $name) {
return $action;
}
}
return null;
}
function getAlertDeliveryHeaderAction(Testable $component, string $name): ?Action
{
$instance = $component->instance();
$instance->cacheInteractsWithHeaderActions();
foreach ($instance->getCachedHeaderActions() as $action) {
if ($action instanceof Action && $action->getName() === $name) {
return $action;
}
}
return null;
}
it('lists only deliveries for entitled tenants', function (): void {
[$user, $tenantA] = createUserWithTenant(role: 'readonly');
$tenantB = Tenant::factory()->create([
'workspace_id' => (int) $tenantA->workspace_id,
]);
$workspaceId = (int) $tenantA->workspace_id;
$rule = AlertRule::factory()->create([
'workspace_id' => $workspaceId,
]);
$destination = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
]);
$tenantADelivery = AlertDelivery::factory()->create([
'workspace_id' => $workspaceId,
'tenant_id' => (int) $tenantA->getKey(),
'alert_rule_id' => (int) $rule->getKey(),
'alert_destination_id' => (int) $destination->getKey(),
'event_type' => 'high_drift',
]);
$tenantBDelivery = AlertDelivery::factory()->create([
'workspace_id' => $workspaceId,
'tenant_id' => (int) $tenantB->getKey(),
'alert_rule_id' => (int) $rule->getKey(),
'alert_destination_id' => (int) $destination->getKey(),
'event_type' => 'compare_failed',
]);
$this->actingAs($user);
Livewire::test(ListAlertDeliveries::class)
->assertCanSeeTableRecords([$tenantADelivery])
->assertCanNotSeeTableRecords([$tenantBDelivery]);
});
it('shows a guided empty state on alert deliveries and links to alert rules', function (): void {
[$user] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
$component = Livewire::test(ListAlertDeliveries::class)
->assertTableEmptyStateActionsExistInOrder(['view_alert_rules'])
->assertSee('No alert deliveries')
->assertSee('Deliveries appear automatically when alert rules fire.');
$table = $component->instance()->getTable();
expect($table->getEmptyStateHeading())->toBe('No alert deliveries');
expect($table->getEmptyStateDescription())->toBe('Deliveries appear automatically when alert rules fire.');
expect($table->getEmptyStateIcon())->toBe('heroicon-o-bell-alert');
$action = getAlertDeliveryEmptyStateAction($component, 'view_alert_rules');
expect($action)->not->toBeNull();
expect($action?->getLabel())->toBe('View alert rules');
expect($action?->getUrl())->toBe(AlertRuleResource::getUrl(panel: 'admin'));
expect(getAlertDeliveryHeaderAction($component, 'view_alert_rules'))->toBeNull();
});
it('keeps alert deliveries header-action free after records exist', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$workspaceId = (int) $tenant->workspace_id;
$rule = AlertRule::factory()->create([
'workspace_id' => $workspaceId,
]);
$destination = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
]);
$delivery = AlertDelivery::factory()->create([
'workspace_id' => $workspaceId,
'tenant_id' => (int) $tenant->getKey(),
'alert_rule_id' => (int) $rule->getKey(),
'alert_destination_id' => (int) $destination->getKey(),
]);
$this->actingAs($user);
$component = Livewire::test(ListAlertDeliveries::class)
->assertCanSeeTableRecords([$delivery]);
expect(getAlertDeliveryHeaderAction($component, 'view_alert_rules'))->toBeNull();
});
it('returns 404 when a member from another workspace tries to view a delivery', function (): void {
[$user] = createUserWithTenant(role: 'owner');
$otherWorkspace = Workspace::factory()->create();
$tenant = Tenant::factory()->create([
'workspace_id' => (int) $otherWorkspace->getKey(),
]);
$rule = AlertRule::factory()->create([
'workspace_id' => (int) $otherWorkspace->getKey(),
]);
$destination = AlertDestination::factory()->create([
'workspace_id' => (int) $otherWorkspace->getKey(),
]);
$delivery = AlertDelivery::factory()->create([
'workspace_id' => (int) $otherWorkspace->getKey(),
'tenant_id' => (int) $tenant->getKey(),
'alert_rule_id' => (int) $rule->getKey(),
'alert_destination_id' => (int) $destination->getKey(),
]);
$this->actingAs($user)
->get(AlertDeliveryResource::getUrl('view', ['record' => $delivery], panel: 'admin'))
->assertNotFound();
});
it('returns 403 for members missing alerts view capability on deliveries index', function (): void {
$workspace = Workspace::factory()->create();
$user = User::factory()->create();
WorkspaceMembership::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'user_id' => (int) $user->getKey(),
'role' => 'readonly',
]);
$resolver = \Mockery::mock(WorkspaceCapabilityResolver::class);
$resolver->shouldReceive('isMember')->andReturnTrue();
$resolver->shouldReceive('can')->andReturnFalse();
app()->instance(WorkspaceCapabilityResolver::class, $resolver);
session()->put(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
$this->actingAs($user)
->get(AlertDeliveryResource::getUrl(panel: 'admin'))
->assertForbidden();
});
it('keeps persisted alert delivery filters inside the active tenant scope', function (): void {
$tenantA = Tenant::factory()->create();
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner');
$tenantB = Tenant::factory()->create([
'workspace_id' => (int) $tenantA->workspace_id,
]);
createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner');
$workspaceId = (int) $tenantA->workspace_id;
$rule = AlertRule::factory()->create([
'workspace_id' => $workspaceId,
]);
$destination = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
]);
$tenantADelivery = AlertDelivery::factory()->create([
'workspace_id' => $workspaceId,
'tenant_id' => (int) $tenantA->getKey(),
'alert_rule_id' => (int) $rule->getKey(),
'alert_destination_id' => (int) $destination->getKey(),
'status' => AlertDelivery::STATUS_SENT,
]);
$tenantBDelivery = AlertDelivery::factory()->create([
'workspace_id' => $workspaceId,
'tenant_id' => (int) $tenantB->getKey(),
'alert_rule_id' => (int) $rule->getKey(),
'alert_destination_id' => (int) $destination->getKey(),
'status' => AlertDelivery::STATUS_SENT,
]);
$this->actingAs($user);
Filament::setTenant($tenantA, true);
Livewire::test(ListAlertDeliveries::class)
->filterTable('status', AlertDelivery::STATUS_SENT)
->assertCanSeeTableRecords([$tenantADelivery])
->assertCanNotSeeTableRecords([$tenantBDelivery]);
Livewire::test(ListAlertDeliveries::class)
->assertSet('tableFilters.status.value', AlertDelivery::STATUS_SENT)
->assertCanSeeTableRecords([$tenantADelivery])
->assertCanNotSeeTableRecords([$tenantBDelivery]);
});