TenantAtlas/tests/Feature/Alerts/AlertDestinationSendTestMessageTest.php
ahmido d49d33ac27 feat(alerts): test message + last test status + deep links (#122)
Implements feature 100 (Alert Targets):

- US1: “Send test message” action (RBAC + confirmation + rate limit + audit + async job)
- US2: Derived “Last test” status badge (Never/Sent/Failed/Pending) on view + edit surfaces
- US3: “View last delivery” deep link + deliveries viewer filters (event_type, destination) incl. tenantless test deliveries

Tests:
- Full suite green (1348 passed, 7 skipped)
- Added focused feature tests for send test, last test resolver/badges, and deep-link filters

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #122
2026-02-18 23:12:38 +00:00

179 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Resources\AlertDestinationResource;
use App\Filament\Resources\AlertDestinationResource\Pages\EditAlertDestination;
use App\Jobs\Alerts\DeliverAlertsJob;
use App\Models\AlertDelivery;
use App\Models\AlertDestination;
use App\Models\AuditLog;
use App\Models\Workspace;
use App\Support\Audit\AuditActionId;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
uses(RefreshDatabase::class);
// ---------------------------------------------------------------------------
// T007 — Happy path: owner sends a test message, delivery + job created
// ---------------------------------------------------------------------------
it('creates a test delivery and dispatches a deliver job on send test message', function (): void {
Queue::fake();
[$user] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
$workspaceId = (int) session()->get(WorkspaceContext::SESSION_KEY);
$destination = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
'is_enabled' => true,
]);
Livewire::test(EditAlertDestination::class, ['record' => $destination->getRouteKey()])
->callAction('send_test_message');
$delivery = AlertDelivery::query()
->where('alert_destination_id', $destination->getKey())
->where('event_type', AlertDelivery::EVENT_TYPE_TEST)
->first();
expect($delivery)->not->toBeNull();
expect($delivery->workspace_id)->toBe($workspaceId);
expect($delivery->tenant_id)->toBeNull();
expect($delivery->alert_rule_id)->toBeNull();
expect($delivery->status)->toBe(AlertDelivery::STATUS_QUEUED);
Queue::assertPushed(DeliverAlertsJob::class, function (DeliverAlertsJob $job) use ($workspaceId): bool {
return $job->workspaceId === $workspaceId;
});
});
// ---------------------------------------------------------------------------
// T008 — Rate-limit refusal: second request within 60 seconds is rejected
// ---------------------------------------------------------------------------
it('refuses a second test message within 60 seconds', function (): void {
Queue::fake();
[$user] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
$workspaceId = (int) session()->get(WorkspaceContext::SESSION_KEY);
$destination = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
'is_enabled' => true,
]);
AlertDelivery::factory()->test()->create([
'workspace_id' => $workspaceId,
'alert_destination_id' => (int) $destination->getKey(),
'created_at' => now()->subSeconds(30),
]);
Livewire::test(EditAlertDestination::class, ['record' => $destination->getRouteKey()])
->callAction('send_test_message')
->assertNotified();
expect(
AlertDelivery::query()
->where('alert_destination_id', $destination->getKey())
->where('event_type', AlertDelivery::EVENT_TYPE_TEST)
->count()
)->toBe(1);
Queue::assertNothingPushed();
});
// ---------------------------------------------------------------------------
// T009 — Authorization: readonly member is forbidden from edit page
// ---------------------------------------------------------------------------
it('forbids readonly members from accessing the edit page for destinations', function (): void {
Queue::fake();
[$user] = createUserWithTenant(role: 'readonly');
$this->actingAs($user);
$workspaceId = (int) session()->get(WorkspaceContext::SESSION_KEY);
$destination = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
'is_enabled' => true,
]);
$this->get(AlertDestinationResource::getUrl('edit', ['record' => $destination], panel: 'admin'))
->assertForbidden();
Queue::assertNothingPushed();
});
// ---------------------------------------------------------------------------
// T010 — Non-member: denied as not found (404)
// ---------------------------------------------------------------------------
it('returns 404 for non-member trying to access edit page with send test action', function (): void {
[$user] = createUserWithTenant(role: 'owner');
$otherWorkspace = Workspace::factory()->create();
$destination = AlertDestination::factory()->create([
'workspace_id' => (int) $otherWorkspace->getKey(),
'is_enabled' => true,
]);
$this->actingAs($user)
->get(AlertDestinationResource::getUrl('edit', ['record' => $destination], panel: 'admin'))
->assertNotFound();
});
// ---------------------------------------------------------------------------
// T011 — Audit log assertion: test request is audit-logged
// ---------------------------------------------------------------------------
it('creates an audit log entry when a test message is sent', function (): void {
Queue::fake();
[$user] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
$workspaceId = (int) session()->get(WorkspaceContext::SESSION_KEY);
$destination = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
'is_enabled' => true,
]);
Livewire::test(EditAlertDestination::class, ['record' => $destination->getRouteKey()])
->callAction('send_test_message');
$auditLog = AuditLog::query()
->where('workspace_id', $workspaceId)
->where('action', AuditActionId::AlertDestinationTestRequested->value)
->where('resource_type', 'alert_destination')
->where('resource_id', (string) $destination->getKey())
->first();
expect($auditLog)->not->toBeNull();
expect($auditLog->actor_id)->toBe((int) $user->getKey());
});
// ---------------------------------------------------------------------------
// T012 — Confirmation requirement: action requires confirmation before execution
// ---------------------------------------------------------------------------
it('requires confirmation before sending a test message', function (): void {
Queue::fake();
[$user] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
$workspaceId = (int) session()->get(WorkspaceContext::SESSION_KEY);
$destination = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
'is_enabled' => true,
]);
Livewire::test(EditAlertDestination::class, ['record' => $destination->getRouteKey()])
->assertActionExists('send_test_message')
->mountAction('send_test_message')
->assertActionMounted('send_test_message');
expect(AlertDelivery::query()->count())->toBe(0);
Queue::assertNothingPushed();
});