where('tenant_id', $tenantId) ->where('action', $action) ->pluck('resource_id') ->map(static fn (string $resourceId): int => (int) $resourceId) ->sort() ->values() ->all(); } it('supports bulk workflow actions and audits each record', function (): void { [$manager, $tenant] = createUserWithTenant(role: 'manager'); $this->actingAs($manager); Filament::setTenant($tenant, true); $findings = Finding::factory() ->count(6) ->for($tenant) ->create([ 'status' => Finding::STATUS_NEW, 'triaged_at' => null, ]); $component = Livewire::test(ListFindings::class); $component ->callTableBulkAction('triage_selected', $findings) ->assertHasNoTableBulkActionErrors(); $findings->each(fn (Finding $finding) => expect($finding->refresh()->status)->toBe(Finding::STATUS_TRIAGED)); expect(findingBulkAuditResourceIds((int) $tenant->getKey(), 'finding.triaged')) ->toEqual($findings->pluck('id')->sort()->values()->all()); $assignee = User::factory()->create(); createUserWithTenant(tenant: $tenant, user: $assignee, role: 'operator'); $assignFindings = Finding::factory() ->count(3) ->for($tenant) ->create([ 'status' => Finding::STATUS_TRIAGED, 'assignee_user_id' => null, 'owner_user_id' => null, ]); $component ->callTableBulkAction('assign_selected', $assignFindings, data: [ 'assignee_user_id' => (int) $assignee->getKey(), 'owner_user_id' => (int) $manager->getKey(), ]) ->assertHasNoTableBulkActionErrors(); $assignFindings->each(function (Finding $finding) use ($assignee, $manager): void { $finding->refresh(); expect((int) $finding->assignee_user_id)->toBe((int) $assignee->getKey()) ->and((int) $finding->owner_user_id)->toBe((int) $manager->getKey()); }); expect(findingBulkAuditResourceIds((int) $tenant->getKey(), 'finding.assigned')) ->toEqual($assignFindings->pluck('id')->sort()->values()->all()); $resolveFindings = Finding::factory() ->count(2) ->for($tenant) ->create([ 'status' => Finding::STATUS_IN_PROGRESS, 'resolved_at' => null, 'resolved_reason' => null, ]); $component ->callTableBulkAction('resolve_selected', $resolveFindings, data: [ 'resolved_reason' => 'fixed', ]) ->assertHasNoTableBulkActionErrors(); $resolveFindings->each(function (Finding $finding): void { $finding->refresh(); expect($finding->status)->toBe(Finding::STATUS_RESOLVED) ->and($finding->resolved_reason)->toBe('fixed') ->and($finding->resolved_at)->not->toBeNull(); }); expect(findingBulkAuditResourceIds((int) $tenant->getKey(), 'finding.resolved')) ->toEqual($resolveFindings->pluck('id')->sort()->values()->all()); $closeFindings = Finding::factory() ->count(2) ->for($tenant) ->create([ 'status' => Finding::STATUS_TRIAGED, 'closed_at' => null, 'closed_reason' => null, ]); $component ->callTableBulkAction('close_selected', $closeFindings, data: [ 'closed_reason' => 'not applicable', ]) ->assertHasNoTableBulkActionErrors(); $closeFindings->each(function (Finding $finding): void { $finding->refresh(); expect($finding->status)->toBe(Finding::STATUS_CLOSED) ->and($finding->closed_reason)->toBe('not applicable') ->and($finding->closed_at)->not->toBeNull() ->and($finding->closed_by_user_id)->not->toBeNull(); }); expect(findingBulkAuditResourceIds((int) $tenant->getKey(), 'finding.closed')) ->toEqual($closeFindings->pluck('id')->sort()->values()->all()); });