create(); [$user] = createUserWithTenant(role: 'owner'); $finding = Finding::factory()->for($tenant)->create(); $this->actingAs($user) ->get(FindingResource::getUrl('index', tenant: $tenant)) ->assertNotFound(); $this->actingAs($user) ->get(FindingResource::getUrl('view', ['record' => $finding], tenant: $tenant)) ->assertNotFound(); }); it('shows triage row action disabled for readonly members', function (): void { [$user, $tenant] = createUserWithTenant(role: 'readonly'); $this->actingAs($user); Filament::setTenant($tenant, true); $finding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_NEW, ]); Livewire::test(ListFindings::class) ->assertTableActionVisible('triage', $finding) ->assertTableActionDisabled('triage', $finding); }); it('enforces 404 for non-member and 403 for member missing capability in workflow service', function (): void { [$owner, $tenant] = createUserWithTenant(role: 'owner'); [$readonly] = createUserWithTenant(tenant: $tenant, role: 'readonly'); $outsider = \App\Models\User::factory()->create(); $finding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_NEW, ]); $service = app(FindingWorkflowService::class); expect(fn () => $service->triage($finding, $tenant, $outsider)) ->toThrow(NotFoundHttpException::class); expect(fn () => $service->triage($finding, $tenant, $readonly)) ->toThrow(AuthorizationException::class); $triaged = $service->triage($finding, $tenant, $owner); expect($triaged->status)->toBe(Finding::STATUS_TRIAGED); }); it('returns 404 and mutates nothing when a forged foreign-tenant finding action is mounted', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); Filament::setTenant($tenant, true); $foreignTenant = \App\Models\Tenant::factory()->create(); $foreignFinding = Finding::factory()->for($foreignTenant)->create([ 'status' => Finding::STATUS_NEW, ]); $component = Livewire::test(ListFindings::class); expect(fn () => $component->instance()->mountTableAction('triage', (string) $foreignFinding->getKey())) ->toThrow(NotFoundHttpException::class); expect($foreignFinding->fresh()?->status)->toBe(Finding::STATUS_NEW); }); it('denies finding view and triage as not found when tenant context workspace does not match the record workspace', function (): void { $tenant = \App\Models\Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $finding = Finding::make([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id + 999, 'status' => Finding::STATUS_NEW, ]); $this->actingAs($user); Filament::setCurrentPanel('admin'); Filament::setTenant(null, true); Filament::bootCurrentPanel(); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); session()->put(WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY, [ (string) $tenant->workspace_id => (int) $tenant->getKey(), ]); expect(Gate::forUser($user)->allows('view', $finding))->toBeFalse(); try { Gate::forUser($user)->authorize('triage', $finding); $this->fail('Expected workspace-mismatched finding authorization to be denied as not found.'); } catch (AuthorizationException $exception) { expect($exception->status())->toBe(404); } });