From 7dfbc06bd10de2412a413f88632ab8a3788e09c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 13 May 2026 08:30:22 +0200 Subject: [PATCH] feat: only handle unsubscribe events for configured list ids --- config/services.yaml | 4 ++ .../MailjetNewsletterWebhookController.php | 18 ++++++ ...MailjetNewsletterWebhookControllerTest.php | 63 ++++++++++++++++++- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/config/services.yaml b/config/services.yaml index 368bf69..6387d70 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -245,6 +245,10 @@ services: arguments: $mailjetLists: '%mailjet_lists%' + App\Controller\Webhook\MailjetNewsletterWebhookController: + arguments: + $mailjetLists: '%mailjet_lists%' + App\Service\DomainConfigProvider: arguments: $domainConfig: '%domain_config%' diff --git a/src/Controller/Webhook/MailjetNewsletterWebhookController.php b/src/Controller/Webhook/MailjetNewsletterWebhookController.php index 8ea9243..09b46fe 100644 --- a/src/Controller/Webhook/MailjetNewsletterWebhookController.php +++ b/src/Controller/Webhook/MailjetNewsletterWebhookController.php @@ -15,9 +15,13 @@ use Symfony\Component\Routing\Attribute\Route; final class MailjetNewsletterWebhookController extends AbstractController { + /** + * @param array $mailjetLists + */ public function __construct( private readonly MessageBusInterface $messageBus, private readonly LoggerInterface $logger, + private readonly array $mailjetLists = [], ) { } @@ -98,6 +102,15 @@ final class MailjetNewsletterWebhookController extends AbstractController return null; } + if (false === $this->isHandledListId($mailjetListId)) { + $this->logger->info('Ignored Mailjet newsletter webhook event for unconfigured list', [ + 'email' => $email, + 'mailjet_list_id' => $mailjetListId, + ]); + + return null; + } + return new MailjetNewsletterEventMessage( email: $email, mailjetListId: $mailjetListId, @@ -126,6 +139,11 @@ final class MailjetNewsletterWebhookController extends AbstractController return null; } + private function isHandledListId(int $mailjetListId): bool + { + return array_key_exists($mailjetListId, $this->mailjetLists); + } + /** * @param array $payload */ diff --git a/tests/Controller/Webhook/MailjetNewsletterWebhookControllerTest.php b/tests/Controller/Webhook/MailjetNewsletterWebhookControllerTest.php index 81be6c7..0d459f4 100644 --- a/tests/Controller/Webhook/MailjetNewsletterWebhookControllerTest.php +++ b/tests/Controller/Webhook/MailjetNewsletterWebhookControllerTest.php @@ -28,7 +28,7 @@ class MailjetNewsletterWebhookControllerTest extends TestCase })) ->willReturnCallback(static fn (object $message): Envelope => new Envelope($message)); - $controller = new MailjetNewsletterWebhookController($messageBus, new NullLogger()); + $controller = new MailjetNewsletterWebhookController($messageBus, new NullLogger(), [123 => 'Newsletter']); $response = $controller->index(Request::create( '/webhooks/mailjet/newsletter', 'POST', @@ -55,12 +55,69 @@ class MailjetNewsletterWebhookControllerTest extends TestCase self::assertSame(1770000000, $dispatchedMessages[0]->eventAt?->getTimestamp()); } + public function testIgnoresEventsForUnconfiguredLists(): void + { + $messageBus = $this->createMock(MessageBusInterface::class); + $messageBus->expects(self::never())->method('dispatch'); + + $controller = new MailjetNewsletterWebhookController($messageBus, new NullLogger(), [999 => 'Newsletter']); + $response = $controller->index(Request::create( + '/webhooks/mailjet/newsletter', + 'POST', + content: json_encode([ + 'event' => MailjetNewsletterEventMessage::EVENT_UNSUBSCRIBE, + 'email' => 'customer@example.com', + 'mj_list_id' => '123', + ], JSON_THROW_ON_ERROR), + )); + + self::assertSame(200, $response->getStatusCode()); + self::assertSame(['success' => true, 'dispatched' => 0], json_decode($response->getContent() ?: '', true, 512, JSON_THROW_ON_ERROR)); + } + + public function testDispatchesOnlyConfiguredEventsFromBatchPayload(): void + { + $dispatchedMessages = []; + $messageBus = $this->createMock(MessageBusInterface::class); + $messageBus + ->expects(self::once()) + ->method('dispatch') + ->with(self::callback(static function (MailjetNewsletterEventMessage $message) use (&$dispatchedMessages): bool { + $dispatchedMessages[] = $message; + + return true; + })) + ->willReturnCallback(static fn (object $message): Envelope => new Envelope($message)); + + $controller = new MailjetNewsletterWebhookController($messageBus, new NullLogger(), [123 => 'Newsletter']); + $response = $controller->index(Request::create( + '/webhooks/mailjet/newsletter', + 'POST', + content: json_encode([ + [ + 'event' => MailjetNewsletterEventMessage::EVENT_UNSUBSCRIBE, + 'email' => 'customer@example.com', + 'mj_list_id' => '123', + ], + [ + 'event' => MailjetNewsletterEventMessage::EVENT_UNSUBSCRIBE, + 'email' => 'customer@example.com', + 'mj_list_id' => '999', + ], + ], JSON_THROW_ON_ERROR), + )); + + self::assertSame(200, $response->getStatusCode()); + self::assertCount(1, $dispatchedMessages); + self::assertSame(123, $dispatchedMessages[0]->mailjetListId); + } + public function testRejectsInvalidJson(): void { $messageBus = $this->createMock(MessageBusInterface::class); $messageBus->expects(self::never())->method('dispatch'); - $controller = new MailjetNewsletterWebhookController($messageBus, new NullLogger()); + $controller = new MailjetNewsletterWebhookController($messageBus, new NullLogger(), [123 => 'Newsletter']); $response = $controller->index(Request::create('/webhooks/mailjet/newsletter', 'POST', content: '{')); self::assertSame(400, $response->getStatusCode()); @@ -71,7 +128,7 @@ class MailjetNewsletterWebhookControllerTest extends TestCase $messageBus = $this->createMock(MessageBusInterface::class); $messageBus->expects(self::never())->method('dispatch'); - $controller = new MailjetNewsletterWebhookController($messageBus, new NullLogger()); + $controller = new MailjetNewsletterWebhookController($messageBus, new NullLogger(), [123 => 'Newsletter']); $response = $controller->index(Request::create( '/webhooks/mailjet/newsletter', 'POST',