diff --git a/.idea/php.xml b/.idea/php.xml
index d547306..69a5912 100644
--- a/.idea/php.xml
+++ b/.idea/php.xml
@@ -179,6 +179,7 @@
+
diff --git a/tests/Service/EmailServiceTest.php b/tests/Service/EmailServiceTest.php
new file mode 100644
index 0000000..64a8971
--- /dev/null
+++ b/tests/Service/EmailServiceTest.php
@@ -0,0 +1,225 @@
+mailer = $this->createMock(MailerInterface::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->urlGenerator = $this->createMock(UrlGeneratorInterface::class);
+ $this->loggerService = $this->createMock(LoggerService::class);
+
+ $this->service = new EmailService(
+ $this->mailer,
+ $this->logger,
+ $this->urlGenerator,
+ $this->loggerService
+ );
+ }
+
+ /**
+ * Helper to set private ID property on entities.
+ */
+ private function setEntityId(object $entity, int $id): void
+ {
+ $reflection = new \ReflectionClass($entity);
+ if ($reflection->hasProperty('id')) {
+ $property = $reflection->getProperty('id');
+ // $property->setAccessible(true); // Uncomment for PHP < 8.1
+ $property->setValue($entity, $id);
+ }
+ }
+
+ // ==========================================
+ // TEST: sendPasswordSetupEmail
+ // ==========================================
+
+ public function testSendPasswordSetupEmailSuccess(): void
+ {
+ // 1. Setup Data
+ $user = new User();
+ $this->setEntityId($user, 10);
+ $user->setEmail('new@user.com');
+
+ // Token format: "o{OrgId}@{RandomHex}"
+ // We use "o50@abcdef" to test that Org ID 50 is correctly extracted
+ $token = 'o50@abcdef123456';
+
+ // 2. Expect URL Generation
+ $this->urlGenerator->expects($this->once())
+ ->method('generate')
+ ->with(
+ 'password_setup',
+ ['id' => 10, 'token' => $token],
+ UrlGeneratorInterface::ABSOLUTE_URL
+ )
+ ->willReturn('https://sudalys.fr/setup/10/token');
+
+ // 3. Expect Mailer Send
+ $this->mailer->expects($this->once())
+ ->method('send')
+ ->with($this->callback(function (TemplatedEmail $email) use ($user, $token) {
+ // Verify Email Construction
+ $context = $email->getContext();
+
+ return $email->getTo()[0]->getAddress() === 'new@user.com'
+ && $email->getSubject() === 'Définissez votre mot de passe'
+ && $email->getHtmlTemplate() === 'emails/password_setup.html.twig'
+ && $context['user'] === $user
+ && $context['token'] === $token
+ && $context['linkUrl'] === 'https://sudalys.fr/setup/10/token';
+ }));
+
+ // 4. Expect Business Log (Success)
+ // Ensure the Org ID '50' was extracted from the token 'o50@...'
+ $this->loggerService->expects($this->once())
+ ->method('logEmailSent')
+ ->with(10, 50, 'Password setup email sent.');
+
+ // 5. Run
+ $this->service->sendPasswordSetupEmail($user, $token);
+ }
+
+ public function testSendPasswordSetupEmailWithoutOrgIdInToken(): void
+ {
+ $user = new User();
+ $this->setEntityId($user, 10);
+ $user->setEmail('user@test.com');
+
+ // Token WITHOUT 'o' prefix -> Org ID should be null
+ $token = 'abcdef123456';
+
+ $this->urlGenerator->method('generate')->willReturn('https://link.com');
+
+ // Verify log receives null for Org ID
+ $this->loggerService->expects($this->once())
+ ->method('logEmailSent')
+ ->with(10, null, 'Password setup email sent.');
+
+ $this->service->sendPasswordSetupEmail($user, $token);
+ }
+
+ public function testSendPasswordSetupEmailHandlesException(): void
+ {
+ $user = new User();
+ $this->setEntityId($user, 10);
+ $user->setEmail('fail@test.com');
+ $token = 'token';
+
+ $this->urlGenerator->method('generate')->willReturn('http://link');
+
+ // Simulate Mailer Failure
+ $this->mailer->expects($this->once())
+ ->method('send')
+ ->willThrowException(new TransportException('SMTP Error'));
+
+ // Expect System Error Log
+ $this->logger->expects($this->once())
+ ->method('error')
+ ->with($this->stringContains('Failed to send password setup email: SMTP Error'));
+
+ // Ensure business log is NOT called (or called depending on where failure happens,
+ // in your code business log is AFTER mailer, so it should NOT be called)
+ $this->loggerService->expects($this->never())->method('logEmailSent');
+
+ // No exception should bubble up (caught in catch block)
+ $this->service->sendPasswordSetupEmail($user, $token);
+ }
+
+ // ==========================================
+ // TEST: sendExistingUserNotificationEmail
+ // ==========================================
+
+ public function testSendExistingUserNotificationEmailSuccess(): void
+ {
+ // 1. Setup Data
+ $user = new User();
+ $this->setEntityId($user, 20);
+ $user->setEmail('existing@user.com');
+
+ $org = new Organizations();
+ $this->setEntityId($org, 99);
+ $org->setName('My Organization');
+
+ $token = 'some-token';
+
+ // 2. Expect URL Generation
+ $this->urlGenerator->expects($this->once())
+ ->method('generate')
+ ->with(
+ 'user_accept',
+ ['id' => 20, 'token' => $token],
+ UrlGeneratorInterface::ABSOLUTE_URL
+ )
+ ->willReturn('https://sudalys.fr/accept/20');
+
+ // 3. Expect Mailer Send
+ $this->mailer->expects($this->once())
+ ->method('send')
+ ->with($this->callback(function (TemplatedEmail $email) use ($org) {
+ return $email->getTo()[0]->getAddress() === 'existing@user.com'
+ && $email->getSubject() === "Invitation à rejoindre l'organisation My Organization"
+ && $email->getContext()['expirationDays'] === 15;
+ }));
+
+ // 4. Expect Business Log
+ $this->loggerService->expects($this->once())
+ ->method('logEmailSent')
+ ->with(20, 99, 'Existing user notification email sent.');
+
+ // 5. Run
+ $this->service->sendExistingUserNotificationEmail($user, $org, $token);
+ }
+
+ public function testSendExistingUserNotificationEmailHandlesException(): void
+ {
+ $user = new User();
+ $this->setEntityId($user, 20);
+ $user->setEmail('fail@user.com');
+
+ $org = new Organizations();
+ $this->setEntityId($org, 99);
+
+ $this->urlGenerator->method('generate')->willReturn('link');
+
+ // In this specific method, your code logs success BEFORE sending email?
+ // Looking at source:
+ // $this->loggerService->logEmailSent(...);
+ // $this->mailer->send($email);
+
+ // So we expect logEmailSent to be called even if mailer fails
+ $this->loggerService->expects($this->once())->method('logEmailSent');
+
+ $this->mailer->method('send')
+ ->willThrowException(new TransportException('Connection refused'));
+
+ // Expect System Error Log
+ $this->logger->expects($this->once())
+ ->method('error')
+ ->with($this->stringContains('Failed to send existing user notification email'));
+
+ $this->service->sendExistingUserNotificationEmail($user, $org, 'token');
+ }
+}
\ No newline at end of file