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
179 lines
6.7 KiB
PHP
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();
|
|
});
|