From dd429ca02ac2cb2b31d7c4778af766081d3d3378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 16 Mar 2026 14:08:35 +0000 Subject: [PATCH] Resolve "Notification aux groupes utilisateurs" --- .../ChillMainBundle/Entity/Notification.php | 8 +- src/Bundle/ChillMainBundle/Entity/User.php | 5 + .../ChillMainBundle/Entity/UserGroup.php | 15 ++ .../SendImmediateNotificationEmailHandler.php | 22 +- .../SendImmediateNotificationEmailMessage.php | 35 ++- .../Notification/Email/NotificationMailer.php | 32 +-- .../Repository/NotificationRepository.php | 2 +- .../Repository/UserGroupRepository.php | 2 +- ...il_non_system_notification_content.md.twig | 4 + ...l_non_system_notification_content.txt.twig | 4 + ...dImmediateNotificationEmailHandlerTest.php | 187 +++++++++++++++ .../Email/NotificationMailTwigContentTest.php | 71 ++++++ .../Email/NotificationMailerTest.php | 223 ++++++++++++++++-- 13 files changed, 562 insertions(+), 48 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationEmailHandler/SendImmediateNotificationEmailHandlerTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailTwigContentTest.php diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index 20773b884..8075ac638 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -215,17 +215,21 @@ class Notification implements TrackUpdateInterface return $this->addressees; } + /** + * @return list + */ public function getAllAddressees(): array { $allUsers = []; foreach ($this->getAddressees() as $user) { - $allUsers[$user->getId()] = $user; + $allUsers['u_'.$user->getId()] = $user; } foreach ($this->getAddresseeUserGroups() as $userGroup) { + $allUsers['ug_'.$userGroup->getId()] = $userGroup; foreach ($userGroup->getUsers() as $user) { - $allUsers[$user->getId()] = $user; + $allUsers['u_'.$user->getId()] = $user; } } diff --git a/src/Bundle/ChillMainBundle/Entity/User.php b/src/Bundle/ChillMainBundle/Entity/User.php index b5539aa83..a273fefd7 100644 --- a/src/Bundle/ChillMainBundle/Entity/User.php +++ b/src/Bundle/ChillMainBundle/Entity/User.php @@ -658,6 +658,11 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter return true; } + public function isUserGroup(): bool + { + return false; + } + private function getNotificationFlagData(string $flag): array { return $this->notificationFlags[$flag] ?? [self::NOTIF_FLAG_IMMEDIATE_EMAIL]; diff --git a/src/Bundle/ChillMainBundle/Entity/UserGroup.php b/src/Bundle/ChillMainBundle/Entity/UserGroup.php index 39df04b31..f6586d4c9 100644 --- a/src/Bundle/ChillMainBundle/Entity/UserGroup.php +++ b/src/Bundle/ChillMainBundle/Entity/UserGroup.php @@ -256,6 +256,21 @@ class UserGroup return true; } + public function isUser(): bool + { + return false; + } + + /** + * Return a locale for the userGroup. + * + * Currently hardcoded, should be replaced by a property. + */ + public function getLocale(): string + { + return 'fr'; + } + public function contains(User $user): bool { return $this->users->contains($user); diff --git a/src/Bundle/ChillMainBundle/Notification/Email/NotificationEmailHandlers/SendImmediateNotificationEmailHandler.php b/src/Bundle/ChillMainBundle/Notification/Email/NotificationEmailHandlers/SendImmediateNotificationEmailHandler.php index b27f16423..26648b5a5 100644 --- a/src/Bundle/ChillMainBundle/Notification/Email/NotificationEmailHandlers/SendImmediateNotificationEmailHandler.php +++ b/src/Bundle/ChillMainBundle/Notification/Email/NotificationEmailHandlers/SendImmediateNotificationEmailHandler.php @@ -14,7 +14,8 @@ namespace Chill\MainBundle\Notification\Email\NotificationEmailHandlers; use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage; use Chill\MainBundle\Notification\Email\NotificationMailer; use Chill\MainBundle\Repository\NotificationRepository; -use Chill\MainBundle\Repository\UserRepository; +use Chill\MainBundle\Repository\UserGroupRepository; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use Symfony\Component\Messenger\Attribute\AsMessageHandler; @@ -24,7 +25,8 @@ readonly class SendImmediateNotificationEmailHandler { public function __construct( private NotificationRepository $notificationRepository, - private UserRepository $userRepository, + private UserRepositoryInterface $userRepository, + private UserGroupRepository $userGroupRepository, private NotificationMailer $notificationMailer, private LoggerInterface $logger, ) {} @@ -36,7 +38,13 @@ readonly class SendImmediateNotificationEmailHandler public function __invoke(SendImmediateNotificationEmailMessage $message): void { $notification = $this->notificationRepository->find($message->getNotificationId()); - $addressee = $this->userRepository->find($message->getAddresseeId()); + if (null !== $message->getUserId()) { + $addressee = $this->userRepository->find($message->getUserId()); + } elseif (null !== $message->getUserGroupId()) { + $addressee = $this->userGroupRepository->find($message->getUserGroupId()); + } else { + throw new \InvalidArgumentException('Addressee not found: nor an user nor a user group'); + } if (null === $notification) { $this->logger->error('[SendImmediateNotificationEmailHandler] Notification not found', [ @@ -48,10 +56,11 @@ readonly class SendImmediateNotificationEmailHandler if (null === $addressee) { $this->logger->error('[SendImmediateNotificationEmailHandler] Addressee not found', [ - 'addressee_id' => $message->getAddresseeId(), + 'user_id' => $message->getUserId(), + 'user_group_id' => $message->getUserGroupId(), ]); - throw new \InvalidArgumentException(sprintf('User with ID %s not found', $message->getAddresseeId())); + throw new \InvalidArgumentException(sprintf('User with ID %s or user group with id %s not found', $message->getUserId(), $message->getUserGroupId())); } try { @@ -59,7 +68,8 @@ readonly class SendImmediateNotificationEmailHandler } catch (\Exception $e) { $this->logger->error('[SendImmediateNotificationEmailHandler] Failed to send email', [ 'notification_id' => $message->getNotificationId(), - 'addressee_id' => $message->getAddresseeId(), + 'user_id' => $message->getUserId(), + 'user_group_id' => $message->getUserGroupId(), 'stacktrace' => $e->getTraceAsString(), ]); throw $e; diff --git a/src/Bundle/ChillMainBundle/Notification/Email/NotificationEmailMessages/SendImmediateNotificationEmailMessage.php b/src/Bundle/ChillMainBundle/Notification/Email/NotificationEmailMessages/SendImmediateNotificationEmailMessage.php index fb9908b21..82bc84ec1 100644 --- a/src/Bundle/ChillMainBundle/Notification/Email/NotificationEmailMessages/SendImmediateNotificationEmailMessage.php +++ b/src/Bundle/ChillMainBundle/Notification/Email/NotificationEmailMessages/SendImmediateNotificationEmailMessage.php @@ -11,20 +11,45 @@ declare(strict_types=1); namespace Chill\MainBundle\Notification\Email\NotificationEmailMessages; +use Chill\MainBundle\Entity\Notification; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserGroup; + readonly class SendImmediateNotificationEmailMessage { + private int $notificationId; + + private ?int $userId; + + private ?int $userGroupId; + public function __construct( - private int $notificationId, - private int $addresseeId, - ) {} + Notification $notification, + UserGroup|User $addressee, + ) { + $this->notificationId = $notification->getId(); + + if ($addressee instanceof User) { + $this->userId = $addressee->getId(); + $this->userGroupId = null; + } else { + $this->userGroupId = $addressee->getId(); + $this->userId = null; + } + } public function getNotificationId(): int { return $this->notificationId; } - public function getAddresseeId(): int + public function getUserId(): ?int { - return $this->addresseeId; + return $this->userId; + } + + public function getUserGroupId(): ?int + { + return $this->userGroupId; } } diff --git a/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php b/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php index 237cb178b..c1eb03c49 100644 --- a/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php +++ b/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php @@ -14,6 +14,7 @@ namespace Chill\MainBundle\Notification\Email; use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\NotificationComment; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserGroup; use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage; use Doctrine\ORM\Event\PostPersistEventArgs; use Psr\Log\LoggerInterface; @@ -26,13 +27,13 @@ use Symfony\Contracts\Translation\TranslatorInterface; // use Symfony\Component\Translation\LocaleSwitcher; -readonly class NotificationMailer +class NotificationMailer { public function __construct( - private MailerInterface $mailer, - private LoggerInterface $logger, - private MessageBusInterface $messageBus, - private TranslatorInterface $translator, + private readonly MailerInterface $mailer, + private readonly LoggerInterface $logger, + private readonly MessageBusInterface $messageBus, + private readonly TranslatorInterface $translator, // private LocaleSwitcher $localeSwitcher, ) {} @@ -100,25 +101,24 @@ readonly class NotificationMailer if (null === $addressee->getEmail()) { continue; } - $this->processNotificationForAddressee($notification, $addressee); } } - private function processNotificationForAddressee(Notification $notification, User $addressee): void + private function processNotificationForAddressee(Notification $notification, User|UserGroup $addressee): void { $notificationType = $notification->getType(); - if ($addressee->isNotificationSendImmediately($notificationType)) { + if ($addressee instanceof UserGroup || $addressee->isNotificationSendImmediately($notificationType)) { $this->scheduleImmediateEmail($notification, $addressee); } } - private function scheduleImmediateEmail(Notification $notification, User $addressee): void + private function scheduleImmediateEmail(Notification $notification, User|UserGroup $addressee): void { $message = new SendImmediateNotificationEmailMessage( - $notification->getId(), - $addressee->getId() + $notification, + $addressee, ); $this->messageBus->dispatch($message); @@ -130,13 +130,17 @@ readonly class NotificationMailer } /** - * This method sends the email but is now called by the immediate notification email message handler. + * Send an email about a Notification. + * + * It is called by immediate notification email message handler: + * + * @see{\Chill\MainBundle\Notification\Email\NotificationEmailHandlers\SendImmediateNotificationEmailHandler} * * @throws TransportExceptionInterface */ - public function sendEmailToAddressee(Notification $notification, User $addressee): void + public function sendEmailToAddressee(Notification $notification, User|UserGroup $addressee): void { - if (null === $addressee->getEmail()) { + if (null === $addressee->getEmail() || '' === $addressee->getEmail()) { return; } diff --git a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php index 99fb57094..3e98c7496 100644 --- a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php @@ -23,7 +23,7 @@ use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; -final class NotificationRepository implements ObjectRepository +class NotificationRepository implements ObjectRepository { private ?Statement $notificationByRelatedEntityAndUserAssociatedStatement = null; diff --git a/src/Bundle/ChillMainBundle/Repository/UserGroupRepository.php b/src/Bundle/ChillMainBundle/Repository/UserGroupRepository.php index 71266f8e5..c7f9c43cc 100644 --- a/src/Bundle/ChillMainBundle/Repository/UserGroupRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/UserGroupRepository.php @@ -18,7 +18,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Symfony\Contracts\Translation\LocaleAwareInterface; -final class UserGroupRepository implements UserGroupRepositoryInterface, LocaleAwareInterface +class UserGroupRepository implements UserGroupRepositoryInterface, LocaleAwareInterface { private readonly EntityRepository $repository; diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content.md.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content.md.twig index 5c07e2ef7..854547c92 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content.md.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content.md.twig @@ -1,5 +1,9 @@ {% apply markdown_to_html %} +{% if dest.isUser %} {{ dest.label }}, +{% else %} +{{ dest.label|localize_translatable_string }}, +{% endif %} {{ notification.sender.label }} a créé une notification pour vous: diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content.txt.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content.txt.twig index 58f138322..b14f77ef7 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content.txt.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content.txt.twig @@ -1,4 +1,8 @@ +{% if dest.isUser %} {{ dest.label }}, +{% else %} +{{ dest.label|localize_translatable_string }}, +{% endif %} {{ notification.sender.label }} a créé une notification pour vous: diff --git a/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationEmailHandler/SendImmediateNotificationEmailHandlerTest.php b/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationEmailHandler/SendImmediateNotificationEmailHandlerTest.php new file mode 100644 index 000000000..da1621390 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationEmailHandler/SendImmediateNotificationEmailHandlerTest.php @@ -0,0 +1,187 @@ +notificationRepository = $this->prophesize(NotificationRepository::class); + $this->userRepository = $this->prophesize(UserRepositoryInterface::class); + $this->userGroupRepository = $this->prophesize(UserGroupRepository::class); + $this->notificationMailer = $this->prophesize(NotificationMailer::class); + + $this->handler = new SendImmediateNotificationEmailHandler( + $this->notificationRepository->reveal(), + $this->userRepository->reveal(), + $this->userGroupRepository->reveal(), + $this->notificationMailer->reveal(), + new NullLogger() + ); + } + + public function testInvokeWithUserAddressee(): void + { + $notificationId = 123; + $userId = 456; + + $notification = $this->prophesize(Notification::class); + $notification->getId()->willReturn($notificationId); + $user = $this->prophesize(User::class); + $user->getId()->willReturn($userId); + + $message = new SendImmediateNotificationEmailMessage($notification->reveal(), $user->reveal()); + + $this->notificationRepository->find($notificationId)->willReturn($notification->reveal()); + $this->userRepository->find($userId)->willReturn($user->reveal()); + + $this->notificationMailer->sendEmailToAddressee($notification->reveal(), $user->reveal()) + ->shouldBeCalledOnce(); + + ($this->handler)($message); + } + + public function testInvokeWithUserGroupAddressee(): void + { + $notificationId = 123; + $userGroupId = 789; + + $notification = $this->prophesize(Notification::class); + $notification->getId()->willReturn($notificationId); + $userGroup = $this->prophesize(UserGroup::class); + $userGroup->getId()->willReturn($userGroupId); + + $message = new SendImmediateNotificationEmailMessage($notification->reveal(), $userGroup->reveal()); + + $this->notificationRepository->find($notificationId)->willReturn($notification->reveal()); + $this->userGroupRepository->find($userGroupId)->willReturn($userGroup->reveal()); + + $this->notificationMailer->sendEmailToAddressee($notification->reveal(), $userGroup->reveal()) + ->shouldBeCalledOnce(); + + ($this->handler)($message); + } + + public function testInvokeThrowsExceptionWhenNotificationNotFound(): void + { + $notificationId = 123; + $userId = 456; + + $notification = $this->prophesize(Notification::class); + $notification->getId()->willReturn($notificationId); + $user = $this->prophesize(User::class); + $user->getId()->willReturn($userId); + + $message = new SendImmediateNotificationEmailMessage($notification->reveal(), $user->reveal()); + + $this->notificationRepository->find($notificationId)->willReturn(null); + $this->userRepository->find($userId)->willReturn($user->reveal()); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('Notification with ID %s not found', $notificationId)); + + ($this->handler)($message); + } + + public function testInvokeThrowsExceptionWhenUserNotFound(): void + { + $notificationId = 123; + $userId = 456; + + $notification = $this->prophesize(Notification::class); + $notification->getId()->willReturn($notificationId); + $user = $this->prophesize(User::class); + $user->getId()->willReturn($userId); + + $message = new SendImmediateNotificationEmailMessage($notification->reveal(), $user->reveal()); + + $this->notificationRepository->find($notificationId)->willReturn($notification->reveal()); + $this->userRepository->find($userId)->willReturn(null); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('User with ID %s or user group with id %s not found', $userId, '')); + + ($this->handler)($message); + } + + public function testInvokeThrowsExceptionWhenUserGroupNotFound(): void + { + $notificationId = 123; + $userGroupId = 789; + + $notification = $this->prophesize(Notification::class); + $notification->getId()->willReturn($notificationId); + $userGroup = $this->prophesize(UserGroup::class); + $userGroup->getId()->willReturn($userGroupId); + + $message = new SendImmediateNotificationEmailMessage($notification->reveal(), $userGroup->reveal()); + + $this->notificationRepository->find($notificationId)->willReturn($notification->reveal()); + $this->userGroupRepository->find($userGroupId)->willReturn(null); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('User with ID %s or user group with id %s not found', '', $userGroupId)); + + ($this->handler)($message); + } + + public function testInvokeRethrowsExceptionWhenMailerFails(): void + { + $notificationId = 123; + $userId = 456; + + $notification = $this->prophesize(Notification::class); + $notification->getId()->willReturn($notificationId); + $user = $this->prophesize(User::class); + $user->getId()->willReturn($userId); + + $message = new SendImmediateNotificationEmailMessage($notification->reveal(), $user->reveal()); + + $this->notificationRepository->find($notificationId)->willReturn($notification->reveal()); + $this->userRepository->find($userId)->willReturn($user->reveal()); + + $exception = new \Exception('Mailer error'); + $this->notificationMailer->sendEmailToAddressee($notification->reveal(), $user->reveal()) + ->willThrow($exception); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Mailer error'); + + ($this->handler)($message); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailTwigContentTest.php b/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailTwigContentTest.php new file mode 100644 index 000000000..23ae05261 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailTwigContentTest.php @@ -0,0 +1,71 @@ +twig = $this->getContainer()->get('twig'); + } + + /** + * @dataProvider provideContent + */ + public function testContent(string $template, array $args): void + { + $actual = $this->twig->render($template, $args); + + self::assertIsString($actual); + } + + public static function provideContent(): iterable + { + $notification = new Notification(); + $notification->setMessage('test message'); + $notification->setSender(new User()); + + $class = new \ReflectionClass($notification); + $method = $class->getProperty('id'); + $method->setValue($notification, 1); + + $txt = '@ChillMain/Notification/email_non_system_notification_content.txt.twig'; + $md = '@ChillMain/Notification/email_non_system_notification_content.md.twig'; + + $user = new User(); + $user->setLocale('fr'); + $user->setLabel('test'); + + $userGroup = new UserGroup(); + $userGroup->setLabel(['fr' => 'test user group']); + + foreach ([$md, $txt] as $template) { + yield 'test with a user for '.$template => [$template, ['notification' => $notification, 'dest' => $user]]; + yield 'test with a group for '.$template => [$template, ['notification' => $notification, 'dest' => $userGroup]]; + } + + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailerTest.php b/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailerTest.php index 173cb8a0e..a13018a7f 100644 --- a/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailerTest.php @@ -9,11 +9,12 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Notification\Email; +namespace Chill\MainBundle\Tests\Notification\Email; use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\NotificationComment; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserGroup; use Chill\MainBundle\Notification\Email\NotificationMailer; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\PostPersistEventArgs; @@ -64,13 +65,22 @@ class NotificationMailerTest extends TestCase // a mail only to user1 and user3 should have been sent $mailer->send(Argument::that(function (Email $email) { foreach ($email->getTo() as $address) { - if ('user1@foo.com' === $address->getAddress() || 'user3@foo.com' === $address->getAddress()) { + if ('user1@foo.com' === $address->getAddress()) { return true; } } return false; - }))->shouldBeCalledTimes(2); + }))->shouldBeCalledTimes(1); + $mailer->send(Argument::that(function (Email $email) { + foreach ($email->getTo() as $address) { + if ('user3@foo.com' === $address->getAddress()) { + return true; + } + } + + return false; + }))->shouldBeCalledTimes(1); $objectManager = $this->prophesize(EntityManagerInterface::class); @@ -121,7 +131,83 @@ class NotificationMailerTest extends TestCase * @throws \ReflectionException * @throws Exception */ - public function testProcessNotificationForAddresseeWithImmediateEmailPreference(): void + public function testPostPersistNotificationToGroup(): void + { + // Create a real notification entity + $notification = new Notification(); + $notification->setType('test_notification_type'); + + // Use reflection to set the ID since it's normally generated by the database + $reflectionNotification = new \ReflectionClass(Notification::class); + $idProperty = $reflectionNotification->getProperty('id'); + $idProperty->setValue($notification, 123); + + // Create a real user entity + $user = new User(); + $user->setEmail('user@example.com'); + $userGroup = new UserGroup(); + $userGroup->addUser($user); + $notification->addAddressee($userGroup); + + // Use reflection to set the ID since it's normally generated by the database + $reflectionUser = new \ReflectionClass($user); + $idProperty = $reflectionUser->getProperty('id'); + $idProperty->setValue($user, 456); + + $reflectionUser = new \ReflectionClass($userGroup); + $idProperty = $reflectionUser->getProperty('id'); + $idProperty->setValue($userGroup, 789); + + // Set notification flags for the user + $user->setNotificationImmediately('test_notification_type', true); + + $messageBus = $this->prophesize(MessageBusInterface::class); + $messageBus->dispatch(Argument::that(fn (SendImmediateNotificationEmailMessage $message) => 123 === $message->getNotificationId() && 456 === $message->getUserId() && null === $message->getUserGroupId()))->willReturn(new Envelope(new \stdClass()))->shouldBeCalled(); + + $messageBus->dispatch(Argument::that(fn (SendImmediateNotificationEmailMessage $message) => 123 === $message->getNotificationId() && null === $message->getUserId() && 789 === $message->getUserGroupId()))->willReturn(new Envelope(new \stdClass()))->shouldBeCalled(); + + $notificationMailer = $this->buildNotificationMailer(null, $messageBus->reveal()); + + $notificationMailer->postPersistNotification($notification, new PostPersistEventArgs($notification, $this->prophesize(EntityManagerInterface::class)->reveal())); + } + + /** + * @throws \ReflectionException + * @throws Exception + */ + public function testPostPersistNotificationWithImmediateEmailPreference(): void + { + // Create a real notification entity + $notification = new Notification(); + $notification->setType('test_notification_type'); + + // Use reflection to set the ID since it's normally generated by the database + $reflectionNotification = new \ReflectionClass(Notification::class); + $idProperty = $reflectionNotification->getProperty('id'); + $idProperty->setValue($notification, 123); + + // Create a real user entity + $user = new User(); + $user->setEmail('user@example.com'); + $notification->addAddressee($user); + + // Use reflection to set the ID since it's normally generated by the database + $reflectionUser = new \ReflectionClass(User::class); + $idProperty = $reflectionUser->getProperty('id'); + $idProperty->setValue($user, 456); + + // Set notification flags for the user + $user->setNotificationImmediately('test_notification_type', true); + + $messageBus = $this->prophesize(MessageBusInterface::class); + $messageBus->dispatch(Argument::that(fn (SendImmediateNotificationEmailMessage $message) => 123 === $message->getNotificationId() && 456 === $message->getUserId() && null === $message->getUserGroupId()))->willReturn(new Envelope(new \stdClass()))->shouldBeCalled(); + + $notificationMailer = $this->buildNotificationMailer(null, $messageBus->reveal()); + + $notificationMailer->postPersistNotification($notification, new PostPersistEventArgs($notification, $this->prophesize(EntityManagerInterface::class)->reveal())); + } + + public function testPostPersistNotificationWithDailyDigestPreference(): void { // Create a real notification entity $notification = new Notification(); @@ -136,6 +222,11 @@ class NotificationMailerTest extends TestCase // Create a real user entity $user = new User(); $user->setEmail('user@example.com'); + // Set notification flags for the user + $user->setNotificationImmediately('test_notification_type', false); + $user->setNotificationDailyDigest('test_notification_type', true); + + $notification->addAddressee($user); // Use reflection to set the ID since it's normally generated by the database $reflectionUser = new \ReflectionClass(User::class); @@ -143,23 +234,15 @@ class NotificationMailerTest extends TestCase $idProperty->setAccessible(true); $idProperty->setValue($user, 456); - // Set notification flags for the user - $user->setNotificationImmediately('test_notification_type', true); + $messageBus = $this->prophesize(MessageBusInterface::class); + $messageBus->dispatch(Argument::that(fn (SendImmediateNotificationEmailMessage $message) => 123 === $message->getNotificationId() && 456 === $message->getUserId() && null === $message->getUserGroupId()))->willReturn(new Envelope(new \stdClass()))->shouldNotBeCalled(); - $messageBus = $this->createMock(MessageBusInterface::class); - $messageBus->expects($this->once()) - ->method('dispatch') - ->with($this->callback(fn (SendImmediateNotificationEmailMessage $message) => 123 === $message->getNotificationId() - && 456 === $message->getAddresseeId())) - ->willReturn(new Envelope(new \stdClass())); + $notificationMailer = $this->buildNotificationMailer( + null, + $messageBus->reveal() + ); - $mailer = $this->buildNotificationMailer(null, $messageBus); - - // Call the method that processes notifications - $reflection = new \ReflectionClass(NotificationMailer::class); - $method = $reflection->getMethod('processNotificationForAddressee'); - $method->setAccessible(true); - $method->invoke($mailer, $notification, $user); + $notificationMailer->postPersistNotification($notification, new PostPersistEventArgs($notification, $this->prophesize(EntityManagerInterface::class)->reveal())); } public function testSendDailyDigest(): void @@ -250,6 +333,108 @@ class NotificationMailerTest extends TestCase $notificationMailer->sendDailyDigest($user, $notifications); } + public function testSendEmailToAddresseeUser(): void + { + $user = new User(); + $user->setEmail('user@example.com'); + $notification = new Notification(); + $notification->setSender(new User()); + $notification->setTitle('Notification 1'); + $notification->setType('test_notification_type'); + $notification->addAddressee($user); + + $mailer = $this->prophesize(MailerInterface::class); + $mailer->send(Argument::that(function ($arg) { + if (!$arg instanceof Email) { + return false; + } + + if ('Notification 1' !== $arg->getSubject()) { + return false; + } + + foreach ($arg->getTo() as $address) { + if ('user@example.com' === $address->getAddress()) { + return true; + } + } + + return false; + }))->shouldBeCalledOnce(); + + $notificationMailer = $this->buildNotificationMailer($mailer->reveal()); + + $notificationMailer->sendEmailToAddressee($notification, $user); + } + + public function testSendEmailToAddresseeGroup(): void + { + $userGroup = new UserGroup(); + $userGroup->setEmail('user@example.com'); + $notification = new Notification(); + $notification->setSender(new User()); + $notification->setTitle('Notification 1'); + $notification->setType('test_notification_type'); + $notification->addAddressee($userGroup); + + $mailer = $this->prophesize(MailerInterface::class); + $mailer->send(Argument::that(function ($arg) { + if (!$arg instanceof Email) { + return false; + } + + if ('Notification 1' !== $arg->getSubject()) { + return false; + } + + foreach ($arg->getTo() as $address) { + if ('user@example.com' === $address->getAddress()) { + return true; + } + } + + return false; + }))->shouldBeCalledOnce(); + + $notificationMailer = $this->buildNotificationMailer($mailer->reveal()); + + $notificationMailer->sendEmailToAddressee($notification, $userGroup); + } + + public function testSendEmailToAddresseeGroupWithNoAddress(): void + { + $userGroup = new UserGroup(); + $notification = new Notification(); + $notification->setSender(new User()); + $notification->setTitle('Notification 1'); + $notification->setType('test_notification_type'); + $notification->addAddressee($userGroup); + + $mailer = $this->prophesize(MailerInterface::class); + $mailer->send(Argument::any())->shouldNotBeCalled(); + + $notificationMailer = $this->buildNotificationMailer($mailer->reveal()); + + $notificationMailer->sendEmailToAddressee($notification, $userGroup); + } + + public function testSendEmailToAddresseeUserWithNoAddress(): void + { + $user = new User(); + $notification = new Notification(); + $notification->setSender(new User()); + $notification->setTitle('Notification 1'); + $notification->setType('test_notification_type'); + $notification->addAddressee($user); + + $mailer = $this->prophesize(MailerInterface::class); + $mailer->send(Argument::any())->shouldNotBeCalled(); + + $notificationMailer = $this->buildNotificationMailer($mailer->reveal()); + + $notificationMailer->sendEmailToAddressee($notification, $user); + } + private function buildNotificationMailer( ?MailerInterface $mailer = null, ?MessageBusInterface $messageBus = null,