From 8045ff03c817074e42623f2f68149387dadf4e3f Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 10 Dec 2025 08:48:54 +0100 Subject: [PATCH] Test for emailService --- .idea/php.xml | 1 + tests/Service/EmailServiceTest.php | 225 +++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 tests/Service/EmailServiceTest.php 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