userRepository = new InMemoryUserRepository(); $this->clock = new class implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-02-10 15:00:00'); } }; $this->handler = new BlockUserHandler($this->userRepository, $this->clock); } #[Test] public function blocksUserSuccessfully(): 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); $command = new BlockUserCommand( userId: (string) $user->id, reason: 'Comportement inapproprié', ); ($this->handler)($command); $updated = $this->userRepository->get($user->id); self::assertSame(StatutCompte::SUSPENDU, $updated->statut); self::assertSame('Comportement inapproprié', $updated->blockedReason); self::assertNotNull($updated->blockedAt); } #[Test] public function throwsWhenUserAlreadySuspendu(): 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('Première raison', new DateTimeImmutable('2026-02-08 10:00:00')); $this->userRepository->save($user); $this->expectException(UtilisateurNonBlocableException::class); ($this->handler)(new BlockUserCommand( userId: (string) $user->id, reason: 'Seconde raison', )); } #[Test] public function throwsWhenUserNotFound(): void { $this->expectException(UserNotFoundException::class); ($this->handler)(new BlockUserCommand( userId: (string) UserId::generate(), reason: 'Raison', )); } }