*/ private array $dispatchedEvents = []; private MessageBusInterface $eventBus; private CommandTester $commandTester; protected function setUp(): void { $this->activationTokenRepository = new InMemoryActivationTokenRepository(); $this->passwordResetTokenRepository = new InMemoryPasswordResetTokenRepository(); $this->userRepository = new InMemoryUserRepository(); $this->clock = new class implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-02-08 10:00:00'); } }; $tenantId = InfraTenantId::fromString(self::TENANT_ID); $this->tenantRegistry = new InMemoryTenantRegistry([ new TenantConfig($tenantId, self::SUBDOMAIN, 'postgresql://localhost/test'), ]); $dispatchedEvents = &$this->dispatchedEvents; $this->eventBus = new class($dispatchedEvents) implements MessageBusInterface { /** @param list $events */ public function __construct(private array &$events) { } public function dispatch(object $message, array $stamps = []): Envelope { $this->events[] = $message; return new Envelope($message); } }; $command = new CreateTestActivationTokenCommand( $this->activationTokenRepository, $this->passwordResetTokenRepository, $this->userRepository, $this->tenantRegistry, $this->clock, $this->eventBus, ); $this->commandTester = new CommandTester($command); } #[Test] public function itCreatesActivationTokenForNewUser(): void { $this->commandTester->execute([ '--email' => 'new@test.com', '--role' => 'PARENT', '--tenant' => self::SUBDOMAIN, ]); self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); $output = $this->commandTester->getDisplay(); self::assertStringContainsString('Test activation token created successfully', $output); self::assertStringContainsString('Activation URL', $output); } #[Test] public function itCreatesActivationTokenForExistingPendingUser(): void { $user = User::creer( email: new Email('pending@test.com'), role: Role::PARENT, tenantId: TenantId::fromString(self::TENANT_ID), schoolName: 'École Test', dateNaissance: null, createdAt: new DateTimeImmutable('2026-01-15 10:00:00'), ); $this->userRepository->save($user); $this->commandTester->execute([ '--email' => 'pending@test.com', '--role' => 'PARENT', '--tenant' => self::SUBDOMAIN, ]); self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); $output = $this->commandTester->getDisplay(); self::assertStringContainsString('already exists', $output); self::assertStringContainsString('Activation URL', $output); self::assertStringNotContainsString('Reset password URL', $output); } #[Test] public function itCreatesPasswordResetTokenForActiveUser(): void { $user = $this->createActiveUser('active@test.com'); $this->userRepository->save($user); $this->commandTester->execute([ '--email' => 'active@test.com', '--role' => 'PARENT', '--tenant' => self::SUBDOMAIN, ]); self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); $output = $this->commandTester->getDisplay(); self::assertStringContainsString('déjà actif', $output); self::assertStringContainsString('Password reset token created successfully', $output); self::assertStringContainsString('Reset password URL', $output); self::assertStringNotContainsString('Activation URL', $output); } #[Test] public function itDispatchesPasswordResetEventForActiveUser(): void { $user = $this->createActiveUser('active@test.com'); $this->userRepository->save($user); $this->commandTester->execute([ '--email' => 'active@test.com', '--role' => 'PARENT', '--tenant' => self::SUBDOMAIN, ]); self::assertCount(1, $this->dispatchedEvents); self::assertInstanceOf(PasswordResetTokenGenerated::class, $this->dispatchedEvents[0]); self::assertSame('active@test.com', $this->dispatchedEvents[0]->email); } #[Test] public function itDoesNotDispatchEventsForNewUser(): void { $this->commandTester->execute([ '--email' => 'new@test.com', '--role' => 'PARENT', '--tenant' => self::SUBDOMAIN, ]); self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); self::assertCount(0, $this->dispatchedEvents); } private function createActiveUser(string $email): User { return User::reconstitute( id: UserId::generate(), email: new Email($email), role: Role::PARENT, tenantId: TenantId::fromString(self::TENANT_ID), schoolName: 'École Test', statut: StatutCompte::ACTIF, dateNaissance: null, createdAt: new DateTimeImmutable('2026-01-15 10:00:00'), hashedPassword: '$argon2id$hashed', activatedAt: new DateTimeImmutable('2026-01-16 10:00:00'), consentementParental: null, ); } }