New_Micro_Learning/backend/tests/Feature/Teams/TeamAuthorizationTest.php

150 خطوط
7.3 KiB
PHP

<?php
namespace Tests\Feature\Teams;
use App\Models\User;
use App\Modules\Identity\Domain\Enums\UserRole;
use App\Modules\Teams\Domain\Team;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class TeamAuthorizationTest extends TestCase
{
use RefreshDatabase;
public function test_manager_only_sees_members_of_managed_teams(): void
{
$manager = User::factory()->create(['role' => UserRole::Manager]);
$otherManager = User::factory()->for($manager->organization)->create(['role' => UserRole::Manager]);
$ownLearner = User::factory()->for($manager->organization)->create(['role' => UserRole::Learner]);
$privateLearner = User::factory()->for($manager->organization)->create(['role' => UserRole::Learner]);
$ownTeam = $this->team($manager, 'Own team');
$privateTeam = $this->team($manager, 'Private team');
$ownTeam->managers()->attach($manager);
$ownTeam->members()->attach($ownLearner);
$privateTeam->managers()->attach($otherManager);
$privateTeam->members()->attach($privateLearner);
Sanctum::actingAs($manager);
$response = $this->getJson('/api/v1/users')->assertOk();
$response->assertJsonFragment(['id' => $ownLearner->getKey()]);
$response->assertJsonMissing(['id' => $privateLearner->getKey()]);
$teams = $this->getJson('/api/v1/teams')->assertOk();
$teams->assertJsonFragment(['id' => $ownTeam->getKey()]);
$teams->assertJsonMissing(['id' => $privateTeam->getKey()]);
}
public function test_designer_can_create_team_and_attach_same_tenant_member_and_manager(): void
{
$designer = User::factory()->create(['role' => UserRole::CourseDesigner]);
$manager = User::factory()->for($designer->organization)->create(['role' => UserRole::Manager]);
$learner = User::factory()->for($designer->organization)->create(['role' => UserRole::Learner]);
Sanctum::actingAs($designer);
$teamId = $this->postJson('/api/v1/teams', ['name' => 'Sales'])->assertCreated()->json('data.id');
$this->putJson('/api/v1/teams/'.$teamId.'/members', ['userId' => $learner->getKey()])->assertOk();
$this->putJson('/api/v1/teams/'.$teamId.'/managers', ['userId' => $manager->getKey()])->assertOk();
$this->assertDatabaseHas('team_memberships', ['team_id' => $teamId, 'user_id' => $learner->getKey()]);
$this->assertDatabaseHas('team_managers', ['team_id' => $teamId, 'user_id' => $manager->getKey()]);
}
public function test_selecting_manager_automatically_attaches_active_direct_reports(): void
{
$designer = User::factory()->create(['role' => UserRole::CourseDesigner]);
$manager = User::factory()->for($designer->organization)->create(['role' => UserRole::Manager]);
$report = User::factory()->for($designer->organization)->create(['role' => UserRole::Learner, 'direct_manager_id' => $manager->getKey()]);
$other = User::factory()->for($designer->organization)->create(['role' => UserRole::Learner]);
Sanctum::actingAs($designer);
$teamId = $this->postJson('/api/v1/teams', ['name' => 'Automatic', 'managerId' => $manager->getKey()])
->assertCreated()
->assertJsonPath('data.managerCount', 1)
->assertJsonPath('data.memberCount', 1)
->json('data.id');
$this->assertDatabaseHas('team_managers', ['team_id' => $teamId, 'user_id' => $manager->getKey()]);
$this->assertDatabaseHas('team_memberships', ['team_id' => $teamId, 'user_id' => $report->getKey()]);
$this->assertDatabaseMissing('team_memberships', ['team_id' => $teamId, 'user_id' => $other->getKey()]);
}
public function test_manager_cannot_mutate_team_membership(): void
{
$manager = User::factory()->create(['role' => UserRole::Manager]);
$learner = User::factory()->for($manager->organization)->create();
$team = $this->team($manager, 'Managed team');
$team->managers()->attach($manager);
Sanctum::actingAs($manager);
$this->putJson('/api/v1/teams/'.$team->getKey().'/members', ['userId' => $learner->getKey()])->assertForbidden();
}
public function test_cross_tenant_user_cannot_be_attached_to_team(): void
{
$designer = User::factory()->create(['role' => UserRole::CourseDesigner]);
$otherUser = User::factory()->create();
$team = $this->team($designer, 'Local team');
Sanctum::actingAs($designer);
$this->putJson('/api/v1/teams/'.$team->getKey().'/members', ['userId' => $otherUser->getKey()])->assertNotFound();
}
public function test_designer_can_read_update_and_detach_team_assignments(): void
{
$designer = User::factory()->create(['role' => UserRole::CourseDesigner]);
$manager = User::factory()->for($designer->organization)->create(['role' => UserRole::Manager]);
$learner = User::factory()->for($designer->organization)->create(['role' => UserRole::Learner]);
$team = $this->team($designer, 'Support');
$team->managers()->attach($manager);
$team->members()->attach($learner);
Sanctum::actingAs($designer);
$this->getJson('/api/v1/teams/'.$team->getKey())
->assertOk()
->assertJsonPath('data.memberCount', 1)
->assertJsonPath('data.managerCount', 1)
->assertJsonPath('data.members.0.id', $learner->getKey())
->assertJsonMissing(['pivot']);
$this->patchJson('/api/v1/teams/'.$team->getKey(), ['name' => 'Support', 'description' => 'Updated'])
->assertOk()
->assertJsonPath('data.description', 'Updated');
$this->deleteJson('/api/v1/teams/'.$team->getKey().'/members/'.$learner->getKey())->assertOk();
$this->deleteJson('/api/v1/teams/'.$team->getKey().'/managers/'.$manager->getKey())->assertOk();
$this->assertDatabaseMissing('team_memberships', ['team_id' => $team->getKey(), 'user_id' => $learner->getKey()]);
$this->assertDatabaseMissing('team_managers', ['team_id' => $team->getKey(), 'user_id' => $manager->getKey()]);
}
public function test_manager_cannot_read_an_unmanaged_team_detail(): void
{
$manager = User::factory()->create(['role' => UserRole::Manager]);
$unmanaged = $this->team($manager, 'Unmanaged');
Sanctum::actingAs($manager);
$this->getJson('/api/v1/teams/'.$unmanaged->getKey())->assertNotFound();
}
public function test_cross_tenant_team_and_detach_targets_are_not_disclosed(): void
{
$designer = User::factory()->create(['role' => UserRole::CourseDesigner]);
$foreignDesigner = User::factory()->create(['role' => UserRole::CourseDesigner]);
$foreignTeam = $this->team($foreignDesigner, 'Foreign');
$localTeam = $this->team($designer, 'Local');
Sanctum::actingAs($designer);
$this->getJson('/api/v1/teams/'.$foreignTeam->getKey())->assertNotFound();
$this->deleteJson('/api/v1/teams/'.$localTeam->getKey().'/members/'.$foreignDesigner->getKey())->assertNotFound();
}
private function team(User $creator, string $name): Team
{
return Team::query()->create([
'organization_id' => $creator->organization_id,
'name' => $name,
'status' => 'active',
'created_by' => $creator->getKey(),
]);
}
}