Files
Classeo/backend/tests/Unit/Administration/Application/Command/UpdateUserRoles/UpdateUserRolesHandlerTest.php
Mathias STRASSER 44ebe5e511 feat: Liaison parents-enfants avec gestion des tuteurs
Les parents doivent pouvoir suivre la scolarité de leurs enfants (notes,
emploi du temps, devoirs). Cela nécessite un lien formalisé entre le
compte parent et le compte élève, géré par les administrateurs.

Le lien est établi soit manuellement via l'interface d'administration,
soit automatiquement lors de l'activation du compte parent lorsque
l'invitation inclut un élève cible. Ce lien conditionne l'accès aux
données scolaires de l'enfant (autorisations vérifiées par un voter
dédié).
2026-02-12 08:38:19 +01:00

231 lines
6.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Unit\Administration\Application\Command\UpdateUserRoles;
use App\Administration\Application\Command\UpdateUserRoles\UpdateUserRolesCommand;
use App\Administration\Application\Command\UpdateUserRoles\UpdateUserRolesHandler;
use App\Administration\Application\Port\ActiveRoleStore;
use App\Administration\Domain\Exception\UserNotFoundException;
use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\Role;
use App\Administration\Domain\Model\User\User;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryUserRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class UpdateUserRolesHandlerTest extends TestCase
{
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';
private const string SCHOOL_NAME = 'École Alpha';
private InMemoryUserRepository $userRepository;
private Clock $clock;
private ActiveRoleStore $activeRoleStore;
private UpdateUserRolesHandler $handler;
protected function setUp(): void
{
$this->userRepository = new InMemoryUserRepository();
$this->clock = new class implements Clock {
public function now(): DateTimeImmutable
{
return new DateTimeImmutable('2026-02-07 10:00:00');
}
};
$this->activeRoleStore = new class implements ActiveRoleStore {
public bool $cleared = false;
public function store(User $user, Role $role): void
{
}
public function get(User $user): ?Role
{
return null;
}
public function clear(User $user): void
{
$this->cleared = true;
}
};
$this->handler = new UpdateUserRolesHandler(
$this->userRepository,
$this->clock,
$this->activeRoleStore,
);
}
#[Test]
public function replacesAllRolesSuccessfully(): void
{
$user = $this->createAndSaveUser(Role::PROF);
$command = new UpdateUserRolesCommand(
userId: (string) $user->id,
roles: [Role::ADMIN->value, Role::SECRETARIAT->value],
);
$result = ($this->handler)($command);
self::assertTrue($result->aLeRole(Role::ADMIN));
self::assertTrue($result->aLeRole(Role::SECRETARIAT));
self::assertFalse($result->aLeRole(Role::PROF));
self::assertCount(2, $result->roles);
}
#[Test]
public function addsNewRolesWithoutRemovingExisting(): void
{
$user = $this->createAndSaveUser(Role::PROF);
$command = new UpdateUserRolesCommand(
userId: (string) $user->id,
roles: [Role::PROF->value, Role::ADMIN->value],
);
$result = ($this->handler)($command);
self::assertTrue($result->aLeRole(Role::PROF));
self::assertTrue($result->aLeRole(Role::ADMIN));
self::assertCount(2, $result->roles);
}
#[Test]
public function throwsWhenRolesArrayIsEmpty(): void
{
$user = $this->createAndSaveUser(Role::PROF);
$command = new UpdateUserRolesCommand(
userId: (string) $user->id,
roles: [],
);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Au moins un rôle est requis.');
($this->handler)($command);
}
#[Test]
public function throwsWhenRoleIsInvalid(): void
{
$user = $this->createAndSaveUser(Role::PROF);
$command = new UpdateUserRolesCommand(
userId: (string) $user->id,
roles: ['ROLE_INEXISTANT'],
);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Rôle invalide');
($this->handler)($command);
}
#[Test]
public function throwsWhenUserNotFound(): void
{
$command = new UpdateUserRolesCommand(
userId: '550e8400-e29b-41d4-a716-446655440099',
roles: [Role::PROF->value],
);
$this->expectException(UserNotFoundException::class);
($this->handler)($command);
}
#[Test]
public function throwsWhenTenantIdDoesNotMatch(): void
{
$user = $this->createAndSaveUser(Role::PROF);
$command = new UpdateUserRolesCommand(
userId: (string) $user->id,
roles: [Role::ADMIN->value],
tenantId: '550e8400-e29b-41d4-a716-446655440099',
);
$this->expectException(UserNotFoundException::class);
($this->handler)($command);
}
#[Test]
public function clearsActiveRoleStoreAfterUpdate(): void
{
$user = $this->createAndSaveUser(Role::PROF);
$command = new UpdateUserRolesCommand(
userId: (string) $user->id,
roles: [Role::ADMIN->value],
);
($this->handler)($command);
self::assertTrue($this->activeRoleStore->cleared);
}
#[Test]
public function savesUserToRepositoryAfterUpdate(): void
{
$user = $this->createAndSaveUser(Role::PROF);
$command = new UpdateUserRolesCommand(
userId: (string) $user->id,
roles: [Role::ADMIN->value, Role::VIE_SCOLAIRE->value],
);
($this->handler)($command);
$found = $this->userRepository->get($user->id);
self::assertTrue($found->aLeRole(Role::ADMIN));
self::assertTrue($found->aLeRole(Role::VIE_SCOLAIRE));
self::assertFalse($found->aLeRole(Role::PROF));
}
#[Test]
public function keepsOnlySpecifiedRolesWhenUserHasMultiple(): void
{
$user = $this->createAndSaveUser(Role::PROF);
$user->attribuerRole(Role::VIE_SCOLAIRE, new DateTimeImmutable('2026-02-02'));
$user->attribuerRole(Role::SECRETARIAT, new DateTimeImmutable('2026-02-03'));
$user->pullDomainEvents();
$this->userRepository->save($user);
$command = new UpdateUserRolesCommand(
userId: (string) $user->id,
roles: [Role::ADMIN->value],
);
$result = ($this->handler)($command);
self::assertCount(1, $result->roles);
self::assertTrue($result->aLeRole(Role::ADMIN));
}
private function createAndSaveUser(Role $role): User
{
$user = User::inviter(
email: new Email('user@example.com'),
role: $role,
tenantId: TenantId::fromString(self::TENANT_ID),
schoolName: self::SCHOOL_NAME,
firstName: 'Jean',
lastName: 'Dupont',
invitedAt: new DateTimeImmutable('2026-02-01 10:00:00'),
);
$user->pullDomainEvents();
$this->userRepository->save($user);
return $user;
}
}