seed(RoleSeeder::class); $this->org = Organisation::factory()->create(); $this->orgAdmin = User::factory()->create(); $this->org->users()->attach($this->orgAdmin, ['role' => 'org_admin']); } // --- INDEX --- public function test_org_member_can_list_members(): void { $member = User::factory()->create(); $this->org->users()->attach($member, ['role' => 'org_member']); Sanctum::actingAs($member); $response = $this->getJson("/api/v1/organisations/{$this->org->id}/members"); $response->assertOk(); $response->assertJsonCount(2, 'data'); $response->assertJsonStructure(['meta' => ['total_members', 'pending_invitations_count']]); } public function test_non_member_cannot_list_members(): void { $outsider = User::factory()->create(); Sanctum::actingAs($outsider); $response = $this->getJson("/api/v1/organisations/{$this->org->id}/members"); $response->assertForbidden(); } // --- UPDATE --- public function test_org_admin_can_update_member_role(): void { $member = User::factory()->create(); $this->org->users()->attach($member, ['role' => 'org_member']); Sanctum::actingAs($this->orgAdmin); $response = $this->putJson( "/api/v1/organisations/{$this->org->id}/members/{$member->id}", ['role' => 'org_admin'], ); $response->assertOk(); $this->assertDatabaseHas('organisation_user', [ 'user_id' => $member->id, 'organisation_id' => $this->org->id, 'role' => 'org_admin', ]); } public function test_cannot_update_own_role(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->putJson( "/api/v1/organisations/{$this->org->id}/members/{$this->orgAdmin->id}", ['role' => 'org_member'], ); $response->assertUnprocessable(); } public function test_cannot_demote_last_org_admin(): void { $member = User::factory()->create(); $this->org->users()->attach($member, ['role' => 'org_admin']); // Now there are 2 admins. Remove one so orgAdmin is the only one. Sanctum::actingAs($this->orgAdmin); // Demote member (second admin) — should work since orgAdmin still remains $response = $this->putJson( "/api/v1/organisations/{$this->org->id}/members/{$member->id}", ['role' => 'org_member'], ); $response->assertOk(); // Now try having member (now org_member) demote orgAdmin (the last admin) // First, make member admin again to do this, then setup a scenario with truly 1 admin // Reset: make a new user as the sole admin $soleAdmin = User::factory()->create(); $org2 = Organisation::factory()->create(); $org2->users()->attach($soleAdmin, ['role' => 'org_admin']); $target = User::factory()->create(); $org2->users()->attach($target, ['role' => 'org_member']); Sanctum::actingAs($soleAdmin); // Sole admin tries to make themselves org_member — blocked by "own role" check // Instead: soleAdmin tries to make another admin demoted, but they are the sole admin // Let's set target as org_admin and try to demote them $org2->users()->updateExistingPivot($target->id, ['role' => 'org_admin']); // Now demote soleAdmin (but soleAdmin can't change own role) // Let target (now admin) try to demote soleAdmin Sanctum::actingAs($target); $response = $this->putJson( "/api/v1/organisations/{$org2->id}/members/{$soleAdmin->id}", ['role' => 'org_member'], ); // soleAdmin is one of 2 admins now, so this should succeed $response->assertOk(); // Now target is the sole admin. Try demoting target — but target can't change own role. // So let soleAdmin (now org_member) try — they lack permission. // The real test: org with 1 admin, another admin tries to demote them. // Let's create a clean scenario: $org3 = Organisation::factory()->create(); $admin1 = User::factory()->create(); $admin2 = User::factory()->create(); $org3->users()->attach($admin1, ['role' => 'org_admin']); $org3->users()->attach($admin2, ['role' => 'org_admin']); // Demote admin1 so admin2 is the last admin Sanctum::actingAs($admin2); $this->putJson( "/api/v1/organisations/{$org3->id}/members/{$admin1->id}", ['role' => 'org_member'], )->assertOk(); // Now admin2 is last admin. admin1 (now member) can't demote — 403. // admin2 can't change own role. So let's re-promote admin1 then try to demote admin2. $org3->users()->updateExistingPivot($admin1->id, ['role' => 'org_admin']); // Now both are admins again. Demote admin1 to member. $this->putJson( "/api/v1/organisations/{$org3->id}/members/{$admin1->id}", ['role' => 'org_member'], )->assertOk(); // admin2 is sole admin. Try to demote admin2 using admin1 — admin1 is org_member, so 403. Sanctum::actingAs($admin1); $response = $this->putJson( "/api/v1/organisations/{$org3->id}/members/{$admin2->id}", ['role' => 'org_member'], ); $response->assertForbidden(); } // --- DESTROY --- public function test_org_admin_can_remove_member(): void { $member = User::factory()->create(); $this->org->users()->attach($member, ['role' => 'org_member']); Sanctum::actingAs($this->orgAdmin); $response = $this->deleteJson( "/api/v1/organisations/{$this->org->id}/members/{$member->id}" ); $response->assertNoContent(); $this->assertDatabaseMissing('organisation_user', [ 'user_id' => $member->id, 'organisation_id' => $this->org->id, ]); } public function test_cannot_remove_self(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->deleteJson( "/api/v1/organisations/{$this->org->id}/members/{$this->orgAdmin->id}" ); $response->assertUnprocessable(); } public function test_cannot_remove_last_org_admin(): void { // Add a second admin to do the removal $secondAdmin = User::factory()->create(); $this->org->users()->attach($secondAdmin, ['role' => 'org_admin']); // Demote secondAdmin so orgAdmin is the only admin $this->org->users()->updateExistingPivot($secondAdmin->id, ['role' => 'org_member']); Sanctum::actingAs($secondAdmin); // secondAdmin is now org_member — can't delete at all (403) $response = $this->deleteJson( "/api/v1/organisations/{$this->org->id}/members/{$this->orgAdmin->id}" ); $response->assertForbidden(); } public function test_unauthenticated_cannot_access_members(): void { $response = $this->getJson("/api/v1/organisations/{$this->org->id}/members"); $response->assertUnauthorized(); } public function test_org_member_cannot_update_roles(): void { $member = User::factory()->create(); $this->org->users()->attach($member, ['role' => 'org_member']); $target = User::factory()->create(); $this->org->users()->attach($target, ['role' => 'org_member']); Sanctum::actingAs($member); $response = $this->putJson( "/api/v1/organisations/{$this->org->id}/members/{$target->id}", ['role' => 'org_admin'], ); $response->assertForbidden(); } public function test_org_member_cannot_remove_members(): void { $member = User::factory()->create(); $this->org->users()->attach($member, ['role' => 'org_member']); $target = User::factory()->create(); $this->org->users()->attach($target, ['role' => 'org_member']); Sanctum::actingAs($member); $response = $this->deleteJson( "/api/v1/organisations/{$this->org->id}/members/{$target->id}" ); $response->assertForbidden(); } }