97 lines
4.0 KiB
PHP
97 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Resources\FindingResource;
|
|
use App\Models\AlertRule;
|
|
use App\Models\Finding;
|
|
use App\Models\TenantMembership;
|
|
use App\Models\User;
|
|
use App\Notifications\Findings\FindingEventNotification;
|
|
use App\Services\Auth\CapabilityResolver;
|
|
use App\Services\Findings\FindingNotificationService;
|
|
use App\Support\Auth\Capabilities;
|
|
use Filament\Facades\Filament;
|
|
use Illuminate\Support\Facades\Gate;
|
|
|
|
it('stores a filament payload with one tenant finding deep link and recipient reason copy', function (): void {
|
|
[$owner, $tenant] = createUserWithTenant(role: 'owner');
|
|
$assignee = User::factory()->create(['name' => 'Assigned Operator']);
|
|
createUserWithTenant(tenant: $tenant, user: $assignee, role: 'operator');
|
|
|
|
$finding = Finding::factory()->for($tenant)->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'status' => Finding::STATUS_TRIAGED,
|
|
'owner_user_id' => (int) $owner->getKey(),
|
|
'assignee_user_id' => (int) $assignee->getKey(),
|
|
]);
|
|
|
|
app(FindingNotificationService::class)->dispatch($finding, AlertRule::EVENT_FINDINGS_ASSIGNED);
|
|
|
|
$notification = $assignee->notifications()
|
|
->where('type', FindingEventNotification::class)
|
|
->latest('id')
|
|
->first();
|
|
|
|
expect($notification)->not->toBeNull()
|
|
->and(data_get($notification?->data, 'format'))->toBe('filament')
|
|
->and(data_get($notification?->data, 'title'))->toBe('Finding assigned')
|
|
->and(data_get($notification?->data, 'actions.0.label'))->toBe('Open finding')
|
|
->and(data_get($notification?->data, 'actions.0.url'))
|
|
->toBe(FindingResource::getUrl('view', ['record' => $finding], panel: 'tenant', tenant: $tenant))
|
|
->and(data_get($notification?->data, 'finding_event.event_type'))->toBe(AlertRule::EVENT_FINDINGS_ASSIGNED)
|
|
->and(data_get($notification?->data, 'finding_event.recipient_reason'))->toBe('new_assignee');
|
|
});
|
|
|
|
it('returns 404 when a finding notification link is opened after tenant access is removed', function (): void {
|
|
[$owner, $tenant] = createUserWithTenant(role: 'owner');
|
|
$assignee = User::factory()->create(['name' => 'Removed Operator']);
|
|
createUserWithTenant(tenant: $tenant, user: $assignee, role: 'operator');
|
|
|
|
$finding = Finding::factory()->for($tenant)->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'status' => Finding::STATUS_TRIAGED,
|
|
'owner_user_id' => (int) $owner->getKey(),
|
|
'assignee_user_id' => (int) $assignee->getKey(),
|
|
]);
|
|
|
|
app(FindingNotificationService::class)->dispatch($finding, AlertRule::EVENT_FINDINGS_ASSIGNED);
|
|
|
|
$url = data_get($assignee->notifications()->latest('id')->first(), 'data.actions.0.url');
|
|
|
|
TenantMembership::query()
|
|
->where('tenant_id', (int) $tenant->getKey())
|
|
->where('user_id', (int) $assignee->getKey())
|
|
->delete();
|
|
|
|
app(CapabilityResolver::class)->clearCache();
|
|
|
|
$this->actingAs($assignee)
|
|
->get($url)
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('returns 403 when a finding notification link is opened by an in-scope member without findings view capability', function (): void {
|
|
[$owner, $tenant] = createUserWithTenant(role: 'owner');
|
|
$assignee = User::factory()->create(['name' => 'Scoped Operator']);
|
|
createUserWithTenant(tenant: $tenant, user: $assignee, role: 'operator');
|
|
|
|
$finding = Finding::factory()->for($tenant)->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'status' => Finding::STATUS_TRIAGED,
|
|
'owner_user_id' => (int) $owner->getKey(),
|
|
'assignee_user_id' => (int) $assignee->getKey(),
|
|
]);
|
|
|
|
app(FindingNotificationService::class)->dispatch($finding, AlertRule::EVENT_FINDINGS_ASSIGNED);
|
|
|
|
$url = data_get($assignee->notifications()->latest('id')->first(), 'data.actions.0.url');
|
|
|
|
Gate::define(Capabilities::TENANT_FINDINGS_VIEW, fn (): bool => false);
|
|
|
|
$this->actingAs($assignee);
|
|
Filament::setTenant($tenant, true);
|
|
|
|
$this->get($url)->assertForbidden();
|
|
});
|