Test for emailService

This commit is contained in:
Charles 2025-12-10 08:48:54 +01:00
parent 14366b5ed4
commit 8045ff03c8
2 changed files with 226 additions and 0 deletions

View File

@ -179,6 +179,7 @@
<path value="$PROJECT_DIR$/vendor/mtdowling/jmespath.php" />
<path value="$PROJECT_DIR$/vendor/psr/http-client" />
<path value="$PROJECT_DIR$/vendor/twig/extra-bundle" />
<path value="$PROJECT_DIR$/vendor/staabm/side-effects-detector" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.2" />

View File

@ -0,0 +1,225 @@
<?php
namespace App\Tests\Service;
use App\Entity\Organizations;
use App\Entity\User;
use App\Service\EmailService;
use App\Service\LoggerService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\Exception\TransportException;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class EmailServiceTest extends TestCase
{
private EmailService $service;
// Mocks
private MockObject|MailerInterface $mailer;
private MockObject|LoggerInterface $logger; // PSR Logger
private MockObject|UrlGeneratorInterface $urlGenerator;
private MockObject|LoggerService $loggerService; // Custom Business Logger
protected function setUp(): void
{
$this->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');
}
}