feat: MailJet list handling with DOI, webhook receiver and API endpoint

addresses #869cut134
This commit is contained in:
Björn Fromme
2026-04-29 09:51:57 +02:00
parent df7885ca1c
commit e4ef2f7adc
46 changed files with 3389 additions and 322 deletions
@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace App\Tests\MessageHandler;
use App\Entity\NewsletterConsent;
use App\Message\MailjetNewsletterEventMessage;
use App\MessageHandler\MailjetNewsletterEventHandler;
use App\Repository\NewsletterConsentRepository;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
class MailjetNewsletterEventHandlerTest extends TestCase
{
public function testUnsubscribeRevokesExistingConsent(): void
{
$consent = new NewsletterConsent('[email protected]', 123);
$consent->markConfirmed(new \DateTimeImmutable('2026-04-29T10:00:00+00:00'));
$repository = $this->createMock(NewsletterConsentRepository::class);
$repository
->expects(self::once())
->method('findOneByEmailAndListId')
->with('[email protected]', 123)
->willReturn($consent);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager->expects(self::never())->method('persist');
$entityManager->expects(self::once())->method('flush');
$handler = new MailjetNewsletterEventHandler($repository, $entityManager);
$handler(new MailjetNewsletterEventMessage(
email: '[email protected]',
mailjetListId: 123,
event: MailjetNewsletterEventMessage::EVENT_UNSUBSCRIBE,
eventAt: new \DateTimeImmutable('2026-04-29T11:00:00+00:00'),
));
self::assertTrue($consent->isRevoked());
self::assertSame('2026-04-29T11:00:00+00:00', $consent->getRevokedAt()?->format(DATE_ATOM));
}
public function testUnsubscribeCreatesRevokedTombstoneForUnknownConsent(): void
{
$persistedConsent = null;
$repository = $this->createMock(NewsletterConsentRepository::class);
$repository
->expects(self::once())
->method('findOneByEmailAndListId')
->with('[email protected]', 123)
->willReturn(null);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager
->expects(self::once())
->method('persist')
->with(self::callback(static function (NewsletterConsent $consent) use (&$persistedConsent): bool {
$persistedConsent = $consent;
return true;
}));
$entityManager->expects(self::once())->method('flush');
$handler = new MailjetNewsletterEventHandler($repository, $entityManager);
$handler(new MailjetNewsletterEventMessage(
email: '[email protected]',
mailjetListId: 123,
event: MailjetNewsletterEventMessage::EVENT_UNSUBSCRIBE,
eventAt: new \DateTimeImmutable('2026-04-29T11:00:00+00:00'),
));
self::assertInstanceOf(NewsletterConsent::class, $persistedConsent);
self::assertSame('[email protected]', $persistedConsent->getEmail());
self::assertSame(123, $persistedConsent->getMailjetListId());
self::assertTrue($persistedConsent->isRevoked());
}
public function testDuplicateOlderUnsubscribeDoesNotRewriteConsent(): void
{
$consent = new NewsletterConsent('[email protected]', 123);
$consent->markRevoked(new \DateTimeImmutable('2026-04-29T11:00:00+00:00'));
$repository = $this->createMock(NewsletterConsentRepository::class);
$repository->method('findOneByEmailAndListId')->willReturn($consent);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager->expects(self::never())->method('flush');
$handler = new MailjetNewsletterEventHandler($repository, $entityManager);
$handler(new MailjetNewsletterEventMessage(
email: '[email protected]',
mailjetListId: 123,
event: MailjetNewsletterEventMessage::EVENT_UNSUBSCRIBE,
eventAt: new \DateTimeImmutable('2026-04-29T10:00:00+00:00'),
));
self::assertSame('2026-04-29T11:00:00+00:00', $consent->getRevokedAt()?->format(DATE_ATOM));
}
public function testOlderUnsubscribeDoesNotRevokeNewerConfirmation(): void
{
$consent = new NewsletterConsent('[email protected]', 123);
$consent->markConfirmed(new \DateTimeImmutable('2026-04-29T11:00:00+00:00'));
$repository = $this->createMock(NewsletterConsentRepository::class);
$repository->method('findOneByEmailAndListId')->willReturn($consent);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager->expects(self::never())->method('flush');
$handler = new MailjetNewsletterEventHandler($repository, $entityManager);
$handler(new MailjetNewsletterEventMessage(
email: '[email protected]',
mailjetListId: 123,
event: MailjetNewsletterEventMessage::EVENT_UNSUBSCRIBE,
eventAt: new \DateTimeImmutable('2026-04-29T10:00:00+00:00'),
));
self::assertTrue($consent->isConfirmed());
self::assertNull($consent->getRevokedAt());
}
public function testDuplicateUnsubscribeWithoutTimestampDoesNotRewriteConsent(): void
{
$consent = new NewsletterConsent('[email protected]', 123);
$consent->markRevoked(new \DateTimeImmutable('2026-04-29T11:00:00+00:00'));
$repository = $this->createMock(NewsletterConsentRepository::class);
$repository->method('findOneByEmailAndListId')->willReturn($consent);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager->expects(self::never())->method('flush');
$handler = new MailjetNewsletterEventHandler($repository, $entityManager);
$handler(new MailjetNewsletterEventMessage(
email: '[email protected]',
mailjetListId: 123,
event: MailjetNewsletterEventMessage::EVENT_UNSUBSCRIBE,
));
self::assertSame('2026-04-29T11:00:00+00:00', $consent->getRevokedAt()?->format(DATE_ATOM));
}
}