userRepository = new InMemoryUserRepository(); $this->clock = new class implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-02-12 10:00:00'); } }; $this->handler = new UnblockUserHandler($this->userRepository, $this->clock); } #[Test] public function unblocksUserSuccessfully(): void { $user = User::inviter( email: new Email('teacher@example.com'), role: Role::PROF, tenantId: TenantId::fromString(self::TENANT_ID), schoolName: 'École Alpha', firstName: 'Jean', lastName: 'Dupont', invitedAt: new DateTimeImmutable('2026-02-01 10:00:00'), ); $user->activer( '$argon2id$hashed', new DateTimeImmutable('2026-02-02 10:00:00'), new ConsentementParentalPolicy($this->clock), ); $user->bloquer('Comportement inapproprié', new DateTimeImmutable('2026-02-10 15:00:00')); $this->userRepository->save($user); $command = new UnblockUserCommand( userId: (string) $user->id, ); ($this->handler)($command); $updated = $this->userRepository->get($user->id); self::assertSame(StatutCompte::ACTIF, $updated->statut); self::assertNull($updated->blockedAt); self::assertNull($updated->blockedReason); } #[Test] public function throwsWhenUserNotSuspendu(): void { $user = User::inviter( email: new Email('teacher@example.com'), role: Role::PROF, tenantId: TenantId::fromString(self::TENANT_ID), schoolName: 'École Alpha', firstName: 'Jean', lastName: 'Dupont', invitedAt: new DateTimeImmutable('2026-02-01 10:00:00'), ); $user->activer( '$argon2id$hashed', new DateTimeImmutable('2026-02-02 10:00:00'), new ConsentementParentalPolicy($this->clock), ); $this->userRepository->save($user); $this->expectException(UtilisateurNonDeblocableException::class); ($this->handler)(new UnblockUserCommand(userId: (string) $user->id)); } #[Test] public function throwsWhenUserNotFound(): void { $this->expectException(UserNotFoundException::class); ($this->handler)(new UnblockUserCommand(userId: (string) UserId::generate())); } }