TenantAtlas/tests/Feature/Filament/Alerts/AlertRuleCrudTest.php
ahmido d32b2115a8 Spec 103: IA semantics (scope vs filter vs targeting) + UI polish (#126)
Implements Spec 103 (IA semantics: Scope vs Filter vs Targeting) across Monitoring + Manage.

Changes
- Monitoring tenant indicator copy: “All tenants” / “Filtered by tenant: …”
- Alerts KPI header resolves tenant via OperateHubShell::activeEntitledTenant() for consistency
- Manage list pages (Alert Rules / Destinations) no longer show tenant indicator
- AlertRule form uses targeting semantics + sections (Rule / Applies to / Delivery)
- Additional UI polish: resource sections, tenant view widgets layout, RBAC progressive disclosure (“Not configured” when empty)

Notes
- US6 (“Add current tenant” convenience button) intentionally skipped (optional P3).

Testing
- CI=1 vendor/bin/sail artisan test tests/Feature/TenantRBAC/ tests/Feature/Onboarding/OnboardingIdentifyTenantTest.php
- vendor/bin/sail bin pint --dirty --format agent

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #126
2026-02-21 00:28:15 +00:00

120 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Resources\AlertRuleResource;
use App\Filament\Resources\AlertRuleResource\Pages\CreateAlertRule;
use App\Filament\Resources\AlertRuleResource\Pages\EditAlertRule;
use App\Models\AlertDestination;
use App\Models\AlertRule;
use App\Support\Workspaces\WorkspaceContext;
use Livewire\Livewire;
it('creates and edits alert rules with attached destinations', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$workspaceId = (int) session()->get(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY);
$destinationA = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
'name' => 'Teams destination',
]);
$destinationB = AlertDestination::factory()->email()->create([
'workspace_id' => $workspaceId,
'name' => 'Email destination',
]);
$this->actingAs($user);
Livewire::test(CreateAlertRule::class)
->fillForm([
'name' => 'Critical drift alerts',
'is_enabled' => true,
'event_type' => 'high_drift',
'minimum_severity' => 'high',
'tenant_scope_mode' => 'allowlist',
'tenant_allowlist' => [(int) $tenant->getKey()],
'cooldown_seconds' => 900,
'quiet_hours_enabled' => true,
'quiet_hours_start' => '22:00',
'quiet_hours_end' => '06:00',
'quiet_hours_timezone' => 'UTC',
'destination_ids' => [(int) $destinationA->getKey(), (int) $destinationB->getKey()],
])
->call('create')
->assertHasNoFormErrors();
$rule = AlertRule::query()->where('name', 'Critical drift alerts')->first();
expect($rule)->not->toBeNull();
expect($rule->tenant_allowlist)->toBe([(int) $tenant->getKey()]);
expect($rule->destinations()->count())->toBe(2);
Livewire::test(EditAlertRule::class, ['record' => $rule->getRouteKey()])
->fillForm([
'name' => 'Critical drift alerts updated',
'is_enabled' => false,
'destination_ids' => [(int) $destinationB->getKey()],
'tenant_allowlist' => [],
'tenant_scope_mode' => 'all',
])
->call('save')
->assertHasNoFormErrors();
$rule->refresh();
expect($rule->name)->toBe('Critical drift alerts updated');
expect((bool) $rule->is_enabled)->toBeFalse();
expect($rule->tenant_scope_mode)->toBe('all');
expect($rule->destinations()->pluck('alert_destinations.id')->all())->toBe([(int) $destinationB->getKey()]);
});
it('shows targeting semantics labels and hides old scope labels on alert rule edit form', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$workspaceId = (int) session()->get(WorkspaceContext::SESSION_KEY);
$destination = AlertDestination::factory()->create(['workspace_id' => $workspaceId]);
$rule = AlertRule::factory()->create([
'workspace_id' => $workspaceId,
'tenant_scope_mode' => 'allowlist',
'tenant_allowlist' => [(int) $tenant->getKey()],
]);
$rule->destinations()->attach((int) $destination->getKey(), ['workspace_id' => $workspaceId]);
$this->actingAs($user);
$this->withSession([
WorkspaceContext::SESSION_KEY => $workspaceId,
])->get(AlertRuleResource::getUrl('edit', ['record' => $rule], panel: 'admin'))
->assertOk()
->assertSee('Applies to tenants')
->assertSee('This rule is workspace-wide. Use this to limit where it applies.')
->assertSee('Selected tenants')
->assertSee('Only these tenants will trigger this rule.')
->assertDontSee('Tenant scope mode')
->assertDontSee('Tenant allowlist');
});
it('shows form section headings on alert rule edit form', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$workspaceId = (int) session()->get(WorkspaceContext::SESSION_KEY);
$destination = AlertDestination::factory()->create(['workspace_id' => $workspaceId]);
$rule = AlertRule::factory()->create([
'workspace_id' => $workspaceId,
]);
$rule->destinations()->attach((int) $destination->getKey(), ['workspace_id' => $workspaceId]);
$this->actingAs($user);
$this->withSession([
WorkspaceContext::SESSION_KEY => $workspaceId,
])->get(AlertRuleResource::getUrl('edit', ['record' => $rule], panel: 'admin'))
->assertOk()
->assertSee('Rule')
->assertSee('Applies to')
->assertSee('Delivery');
});