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; } }