seed(RoleSeeder::class); } // --- INDEX --- public function test_super_admin_can_list_all_organisations(): void { $admin = User::factory()->create(); $admin->assignRole('super_admin'); Organisation::factory()->count(3)->create(); Sanctum::actingAs($admin); $response = $this->getJson('/api/v1/organisations'); $response->assertOk(); $this->assertCount(3, $response->json('data')); } public function test_org_member_sees_only_own_organisations(): void { $user = User::factory()->create(); $ownOrg = Organisation::factory()->create(); Organisation::factory()->create(); // other org $ownOrg->users()->attach($user, ['role' => 'org_member']); Sanctum::actingAs($user); $response = $this->getJson('/api/v1/organisations'); $response->assertOk(); $this->assertCount(1, $response->json('data')); $this->assertEquals($ownOrg->id, $response->json('data.0.id')); } public function test_unauthenticated_user_cannot_list_organisations(): void { $response = $this->getJson('/api/v1/organisations'); $response->assertUnauthorized(); } // --- SHOW --- public function test_org_member_can_view_own_organisation(): void { $user = User::factory()->create(); $org = Organisation::factory()->create(); $org->users()->attach($user, ['role' => 'org_member']); Sanctum::actingAs($user); $response = $this->getJson("/api/v1/organisations/{$org->id}"); $response->assertOk() ->assertJson(['data' => ['id' => $org->id]]); } public function test_user_cannot_view_other_organisation(): void { $user = User::factory()->create(); $org = Organisation::factory()->create(); Sanctum::actingAs($user); $response = $this->getJson("/api/v1/organisations/{$org->id}"); $response->assertForbidden(); } // --- STORE --- public function test_super_admin_can_create_organisation(): void { $admin = User::factory()->create(); $admin->assignRole('super_admin'); Sanctum::actingAs($admin); $response = $this->postJson('/api/v1/organisations', [ 'name' => 'Test Org', 'slug' => 'test-org', ]); $response->assertCreated() ->assertJson(['data' => ['name' => 'Test Org', 'slug' => 'test-org']]); $this->assertDatabaseHas('organisations', ['slug' => 'test-org']); } public function test_store_attaches_creator_as_org_admin(): void { $admin = User::factory()->create(); $admin->assignRole('super_admin'); Sanctum::actingAs($admin); $response = $this->postJson('/api/v1/organisations', [ 'name' => 'New Org', 'slug' => 'new-org', ]); $response->assertCreated(); $org = Organisation::where('slug', 'new-org')->first(); $this->assertDatabaseHas('organisation_user', [ 'user_id' => $admin->id, 'organisation_id' => $org->id, 'role' => 'org_admin', ]); } public function test_non_admin_cannot_create_organisation(): void { $user = User::factory()->create(); Sanctum::actingAs($user); $response = $this->postJson('/api/v1/organisations', [ 'name' => 'Test Org', 'slug' => 'test-org', ]); $response->assertForbidden(); } public function test_store_with_invalid_data_returns_422(): void { $admin = User::factory()->create(); $admin->assignRole('super_admin'); Sanctum::actingAs($admin); $response = $this->postJson('/api/v1/organisations', []); $response->assertUnprocessable() ->assertJsonValidationErrors(['name', 'slug']); } public function test_store_with_duplicate_slug_returns_422(): void { $admin = User::factory()->create(); $admin->assignRole('super_admin'); Organisation::factory()->create(['slug' => 'taken-slug']); Sanctum::actingAs($admin); $response = $this->postJson('/api/v1/organisations', [ 'name' => 'New Org', 'slug' => 'taken-slug', ]); $response->assertUnprocessable() ->assertJsonValidationErrors(['slug']); } // --- UPDATE --- public function test_org_admin_can_update_organisation(): void { $user = User::factory()->create(); $org = Organisation::factory()->create(); $org->users()->attach($user, ['role' => 'org_admin']); Sanctum::actingAs($user); $response = $this->putJson("/api/v1/organisations/{$org->id}", [ 'name' => 'Updated Name', ]); $response->assertOk() ->assertJson(['data' => ['name' => 'Updated Name']]); } public function test_org_member_cannot_update_organisation(): void { $user = User::factory()->create(); $org = Organisation::factory()->create(); $org->users()->attach($user, ['role' => 'org_member']); Sanctum::actingAs($user); $response = $this->putJson("/api/v1/organisations/{$org->id}", [ 'name' => 'Hacked Name', ]); $response->assertForbidden(); } public function test_non_member_cannot_update_organisation(): void { $user = User::factory()->create(); $org = Organisation::factory()->create(); Sanctum::actingAs($user); $response = $this->putJson("/api/v1/organisations/{$org->id}", [ 'name' => 'Hacked Name', ]); $response->assertForbidden(); } public function test_org_admin_can_update_all_contact_fields(): void { $user = User::factory()->create(); $org = Organisation::factory()->create(); $org->users()->attach($user, ['role' => 'org_admin']); Sanctum::actingAs($user); $response = $this->putJson("/api/v1/organisations/{$org->id}", [ 'contact_name' => 'Bert Hausmans', 'contact_email' => 'bert@example.com', 'phone' => '+31 6 12345678', 'website' => 'https://example.com', ]); $response->assertOk() ->assertJson(['data' => [ 'contact_name' => 'Bert Hausmans', 'contact_email' => 'bert@example.com', 'phone' => '+31 6 12345678', 'website' => 'https://example.com', ]]); $this->assertDatabaseHas('organisations', [ 'id' => $org->id, 'contact_email' => 'bert@example.com', ]); } public function test_update_returns_422_for_invalid_contact_email(): void { $user = User::factory()->create(); $org = Organisation::factory()->create(); $org->users()->attach($user, ['role' => 'org_admin']); Sanctum::actingAs($user); $response = $this->putJson("/api/v1/organisations/{$org->id}", [ 'contact_email' => 'not-an-email', ]); $response->assertUnprocessable() ->assertJsonValidationErrors(['contact_email']); } public function test_update_returns_422_for_invalid_website_url(): void { $user = User::factory()->create(); $org = Organisation::factory()->create(); $org->users()->attach($user, ['role' => 'org_admin']); Sanctum::actingAs($user); $response = $this->putJson("/api/v1/organisations/{$org->id}", [ 'website' => 'not a url', ]); $response->assertUnprocessable() ->assertJsonValidationErrors(['website']); } public function test_update_logs_activity_for_changed_fields(): void { $user = User::factory()->create(); $org = Organisation::factory()->create(['name' => 'Old Name']); $org->users()->attach($user, ['role' => 'org_admin']); Sanctum::actingAs($user); Activity::query()->delete(); $this->putJson("/api/v1/organisations/{$org->id}", [ 'name' => 'New Name', 'contact_email' => 'new@example.com', ])->assertOk(); $activities = Activity::where('log_name', 'organisation') ->where('subject_type', 'organisation') ->where('subject_id', $org->id) ->get(); $this->assertCount(1, $activities); $changes = $activities->first()->attribute_changes; $this->assertSame('New Name', $changes['attributes']['name']); $this->assertSame('Old Name', $changes['old']['name']); $this->assertSame('new@example.com', $changes['attributes']['contact_email']); } public function test_update_does_not_log_activity_when_no_tracked_fields_change(): void { $user = User::factory()->create(); $org = Organisation::factory()->create(['name' => 'Stable Name']); $org->users()->attach($user, ['role' => 'org_admin']); Sanctum::actingAs($user); Activity::query()->delete(); $this->putJson("/api/v1/organisations/{$org->id}", [ 'name' => 'Stable Name', ])->assertOk(); $this->assertSame(0, Activity::where('log_name', 'organisation')->count()); } }