for($tenant)->create([ 'status' => Finding::STATUS_NEW, 'owner_user_id' => (int) $ownerOnly->getKey(), 'assignee_user_id' => null, ]); $assignedFinding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_TRIAGED, 'owner_user_id' => (int) $listOwner->getKey(), 'assignee_user_id' => (int) $listAssignee->getKey(), ]); $assigneeOnlyFinding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_IN_PROGRESS, 'owner_user_id' => null, 'assignee_user_id' => (int) $assigneeOnly->getKey(), ]); $bothNullFinding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_REOPENED, 'owner_user_id' => null, 'assignee_user_id' => null, ]); $sameUserFinding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_NEW, 'owner_user_id' => (int) $samePerson->getKey(), 'assignee_user_id' => (int) $samePerson->getKey(), ]); $this->actingAs($viewer); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::test(ListFindings::class) ->assertCanSeeTableRecords([ $ownerOnlyFinding, $assignedFinding, $assigneeOnlyFinding, $bothNullFinding, $sameUserFinding, ]) ->assertSee('Responsibility') ->assertSee('Accountable owner') ->assertSee('Active assignee') ->assertSee('owned but unassigned') ->assertSee('assigned') ->assertSee('orphaned accountability') ->assertSee('Owner Only') ->assertSee('List Owner') ->assertSee('List Assignee') ->assertSee('Assignee Only') ->assertSee('Same Person'); Livewire::test(ViewFinding::class, ['record' => $assigneeOnlyFinding->getKey()]) ->assertSee('Responsibility state') ->assertSee('orphaned accountability') ->assertSee('Accountable owner') ->assertSee('Active assignee') ->assertSee('Assignee Only'); Livewire::test(ViewFinding::class, ['record' => $sameUserFinding->getKey()]) ->assertSee('assigned') ->assertSee('Same Person'); }); it('isolates owner accountability and assigned work with separate personal filters', function (): void { [$viewer, $tenant] = createUserWithTenant(role: 'manager'); $otherOwner = tenantFindingUser($tenant, 'Other Owner'); $otherAssignee = tenantFindingUser($tenant, 'Other Assignee'); $assignedToMe = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_NEW, 'owner_user_id' => (int) $otherOwner->getKey(), 'assignee_user_id' => (int) $viewer->getKey(), ]); $ownedByMe = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_TRIAGED, 'owner_user_id' => (int) $viewer->getKey(), 'assignee_user_id' => (int) $otherAssignee->getKey(), ]); $bothMine = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_REOPENED, 'owner_user_id' => (int) $viewer->getKey(), 'assignee_user_id' => (int) $viewer->getKey(), ]); $neitherMine = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_IN_PROGRESS, 'owner_user_id' => (int) $otherOwner->getKey(), 'assignee_user_id' => (int) $otherAssignee->getKey(), ]); $this->actingAs($viewer); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::test(ListFindings::class) ->filterTable('my_assigned', true) ->assertCanSeeTableRecords([$assignedToMe, $bothMine]) ->assertCanNotSeeTableRecords([$ownedByMe, $neitherMine]) ->removeTableFilter('my_assigned') ->filterTable('my_accountability', true) ->assertCanSeeTableRecords([$ownedByMe, $bothMine]) ->assertCanNotSeeTableRecords([$assignedToMe, $neitherMine]); }); it('keeps exception ownership visibly separate from finding ownership', function (): void { [$owner, $tenant] = createUserWithTenant(role: 'owner'); $findingOwner = tenantFindingUser($tenant, 'Finding Owner'); $findingAssignee = tenantFindingUser($tenant, 'Finding Assignee'); $exceptionOwner = tenantFindingUser($tenant, 'Exception Owner'); $findingWithException = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_NEW, 'owner_user_id' => (int) $findingOwner->getKey(), 'assignee_user_id' => (int) $findingAssignee->getKey(), ]); FindingException::query()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'finding_id' => (int) $findingWithException->getKey(), 'requested_by_user_id' => (int) $owner->getKey(), 'owner_user_id' => (int) $exceptionOwner->getKey(), 'status' => FindingException::STATUS_PENDING, 'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT, 'request_reason' => 'Needs temporary governance coverage.', 'requested_at' => now(), 'review_due_at' => now()->addDays(7), 'evidence_summary' => ['reference_count' => 0], ]); $requestFinding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_NEW, 'owner_user_id' => (int) $findingOwner->getKey(), ]); $this->actingAs($owner); $tenant->makeCurrent(); Filament::setTenant($tenant, true); $this->get(FindingResource::getUrl('view', ['record' => $findingWithException], panel: 'tenant', tenant: $tenant)) ->assertSuccessful() ->assertSee('Accountable owner') ->assertSee('Active assignee') ->assertSee('Exception owner') ->assertSee('Finding Owner') ->assertSee('Finding Assignee') ->assertSee('Exception Owner'); $component = Livewire::test(ViewFinding::class, ['record' => $requestFinding->getKey()]) ->mountAction('request_exception'); $method = new \ReflectionMethod($component->instance(), 'getMountedActionForm'); $method->setAccessible(true); $form = $method->invoke($component->instance()); $field = collect($form?->getFlatFields(withHidden: true) ?? []) ->first(fn (Field $field): bool => $field->getName() === 'owner_user_id'); $helperText = collect($field?->getChildSchema(Field::BELOW_CONTENT_SCHEMA_KEY)?->getComponents() ?? []) ->filter(fn (mixed $schemaComponent): bool => $schemaComponent instanceof Text) ->map(fn (Text $schemaComponent): string => (string) $schemaComponent->getContent()) ->implode(' '); expect($field?->getLabel())->toBe('Exception owner') ->and($helperText)->toContain('Owns the exception record') ->and($helperText)->toContain('not the finding outcome'); }); it('allows in-scope members and returns 404 for non-members on tenant findings routes', function (): void { $tenant = Tenant::factory()->create(); [$member, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $finding = Finding::factory()->for($tenant)->create(); $this->actingAs($member) ->get(FindingResource::getUrl('index', panel: 'tenant', tenant: $tenant)) ->assertSuccessful(); $this->actingAs($member) ->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'tenant', tenant: $tenant)) ->assertSuccessful(); $tenantInSameWorkspace = Tenant::factory()->create([ 'workspace_id' => (int) $tenant->workspace_id, ]); [$outsider] = createUserWithTenant(tenant: $tenantInSameWorkspace, role: 'owner'); $this->actingAs($outsider) ->get(FindingResource::getUrl('index', panel: 'tenant', tenant: $tenant)) ->assertNotFound(); $this->actingAs($outsider) ->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'tenant', tenant: $tenant)) ->assertNotFound(); }); function tenantFindingUser(Tenant $tenant, string $name): User { $user = User::factory()->create([ 'name' => $name, ]); createUserWithTenant(tenant: $tenant, user: $user, role: 'operator'); return $user; }