admin = User::factory()->create(); $role = Role::query()->create(['name' => 'Scoped inviter', 'client_scoped' => true]); RolePermission::query()->create(['role_id' => $role->id, 'permission' => 'create_clients']); $this->scoped = User::factory()->create(['role_id' => $role->id]); $mine = User::factory()->client()->create(); $this->scoped->assignedClients()->sync([$mine->id]); $this->myGroup = Group::query()->create(['name' => 'My clients']); $this->myGroup->members()->attach($mine->id); $this->otherGroup = Group::query()->create(['name' => 'Board']); $this->sentByMe = Invitation::issue('mine@example.test', 'Mine', null, $this->scoped, now()->addDay()); $this->intoMyGroup = Invitation::issue('colleague@example.test', 'Into my group', $this->myGroup, $this->admin, now()->addDay()); $this->notMine = Invitation::issue('board@example.test', 'Board member', $this->otherGroup, $this->admin, now()->addDay()); $this->noGroup = Invitation::issue('nogroup@example.test', 'No group', null, $this->admin, now()->addDay()); }); test('a client-scoped staff member lists only the invitations within their reach', function () { $this->actingAs($this->scoped)->get('/clients/invitations') ->assertOk() ->assertInertia(fn (AssertableInertia $page) => $page->where( 'invitations', fn ($rows) => collect($rows)->pluck('email')->sort()->values()->all() === ['colleague@example.test', 'mine@example.test'], )); }); test('a client-scoped staff member cannot revoke an invitation outside their reach', function () { $this->actingAs($this->scoped)->delete("/clients/invitations/{$this->notMine->id}")->assertNotFound(); $this->actingAs($this->scoped)->delete("/clients/invitations/{$this->noGroup->id}")->assertNotFound(); expect($this->notMine->fresh()->status)->toBe(Invitation::STATUS_PENDING) ->and($this->noGroup->fresh()->status)->toBe(Invitation::STATUS_PENDING); }); test('a client-scoped staff member can still revoke their own, and one into their group', function () { $this->actingAs($this->scoped)->delete("/clients/invitations/{$this->sentByMe->id}")->assertRedirect(); $this->actingAs($this->scoped)->delete("/clients/invitations/{$this->intoMyGroup->id}")->assertRedirect(); expect($this->sentByMe->fresh()->status)->toBe(Invitation::STATUS_REVOKED) ->and($this->intoMyGroup->fresh()->status)->toBe(Invitation::STATUS_REVOKED); }); test('an unscoped staff member still sees and revokes every invitation', function () { $this->actingAs($this->admin)->get('/clients/invitations') ->assertInertia(fn (AssertableInertia $page) => $page->has('invitations', 4)); $this->actingAs($this->admin)->delete("/clients/invitations/{$this->notMine->id}")->assertRedirect(); expect($this->notMine->fresh()->status)->toBe(Invitation::STATUS_REVOKED); });