287 lines
9.7 KiB
PHP
287 lines
9.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Service\Teamer;
|
|
|
|
use App\Email\Mailer;
|
|
use App\Entity\Teamer;
|
|
use App\Entity\User;
|
|
use App\Message\SendTeamerMailing;
|
|
use App\Model\TeamerFilterDto;
|
|
use App\Model\TeamerMailingDto;
|
|
use App\Model\TeamerMailingRecipient;
|
|
use App\Model\TeamerMailingRecipientsDto;
|
|
use App\Repository\TeamerRepository;
|
|
use App\Service\Teamer\TeamerMailingService;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
class TeamerMailingServiceTest extends TestCase
|
|
{
|
|
private TeamerRepository&MockObject $teamerRepository;
|
|
private Mailer&MockObject $mailer;
|
|
private TeamerMailingService $service;
|
|
private static int $nextId = 1;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->teamerRepository = $this->createMock(TeamerRepository::class);
|
|
$this->mailer = $this->createMock(Mailer::class);
|
|
|
|
$this->service = new TeamerMailingService(
|
|
$this->teamerRepository,
|
|
$this->mailer,
|
|
$this->createMock(LoggerInterface::class),
|
|
'mailing',
|
|
);
|
|
}
|
|
|
|
public function testRenderReplacesAllNamePlaceholders(): void
|
|
{
|
|
$recipient = $this->createRecipient('Anna', 'Berg', '[email protected]');
|
|
|
|
$rendered = $this->service->render(
|
|
'Hallo {{vorname}} {{nachname}}, alias {{name}}!',
|
|
$this->service->placeholderValuesFor($recipient)
|
|
);
|
|
|
|
$this->assertSame('Hallo Anna Berg, alias Anna Berg!', $rendered);
|
|
}
|
|
|
|
public function testRenderLeavesUnknownPlaceholdersUntouched(): void
|
|
{
|
|
$recipient = $this->createRecipient('Anna', 'Berg', '[email protected]');
|
|
|
|
$rendered = $this->service->render(
|
|
'Hallo {{vorname}}, {{unbekannt}} bleibt stehen',
|
|
$this->service->placeholderValuesFor($recipient)
|
|
);
|
|
|
|
$this->assertSame('Hallo Anna, {{unbekannt}} bleibt stehen', $rendered);
|
|
}
|
|
|
|
public function testRecipientIsBuiltFromARepositoryRow(): void
|
|
{
|
|
$deletedAt = new \DateTimeImmutable('2026-01-02 03:04:05');
|
|
|
|
$recipient = TeamerMailingRecipient::fromRow([
|
|
'id' => 42,
|
|
'firstName' => 'Anna',
|
|
'lastName' => 'Berg',
|
|
'email' => '[email protected]',
|
|
'deletedAt' => $deletedAt,
|
|
'disabledAt' => null,
|
|
]);
|
|
|
|
$this->assertSame(42, $recipient->getId());
|
|
$this->assertSame('Anna Berg', $recipient->getFullName());
|
|
$this->assertSame('[email protected]', $recipient->getEmail());
|
|
$this->assertTrue($recipient->isDeleted());
|
|
$this->assertFalse($recipient->isDisabled());
|
|
}
|
|
|
|
public function testResolveRecipientsPartitionsBySkipReason(): void
|
|
{
|
|
$eligible = $this->createRecipient('Anna', 'Berg', '[email protected]');
|
|
$deleted = $this->createRecipient('Bea', 'Ohm', '[email protected]', deleted: true);
|
|
$disabled = $this->createRecipient('Cem', 'Dal', '[email protected]', disabled: true);
|
|
$noEmail = $this->createRecipient('Dana', 'Elf', null);
|
|
$blankEmail = $this->createRecipient('Emil', 'Fux', ' ');
|
|
|
|
$this->teamerRepository
|
|
->method('getMailingRecipients')
|
|
->willReturn([$eligible, $deleted, $disabled, $noEmail, $blankEmail])
|
|
;
|
|
|
|
$recipients = $this->service->resolveRecipients(new TeamerFilterDto());
|
|
|
|
$this->assertSame([$eligible], $recipients->getEligible());
|
|
$this->assertSame(1, $recipients->getSkippedDeleted());
|
|
$this->assertSame(1, $recipients->getSkippedDisabled());
|
|
$this->assertSame(2, $recipients->getSkippedNoEmail());
|
|
$this->assertSame(5, $recipients->getTotal());
|
|
}
|
|
|
|
public function testSendMailingPersonalisesSubjectAndMessagePerRecipient(): void
|
|
{
|
|
$sent = [];
|
|
$this->mailer
|
|
->expects($this->exactly(2))
|
|
->method('createAndSendEmail')
|
|
->willReturnCallback(function (array $context, array $options) use (&$sent): void {
|
|
$sent[] = [$options['to'], $options['subject'], $context['message']];
|
|
})
|
|
;
|
|
|
|
$this->service->sendMailing($this->createMailing(
|
|
'Hallo {{vorname}}',
|
|
'Servus {{name}}',
|
|
$this->createRecipient('Anna', 'Berg', '[email protected]'),
|
|
$this->createRecipient('Bea', 'Ohm', '[email protected]')
|
|
));
|
|
|
|
$this->assertSame([
|
|
['[email protected]', 'Hallo Anna', 'Servus Anna Berg'],
|
|
['[email protected]', 'Hallo Bea', 'Servus Bea Ohm'],
|
|
], $sent);
|
|
}
|
|
|
|
/**
|
|
* Without both transports a mailing silently leaves over the webhoster's relay on the
|
|
* shared queue - it still arrives, so nothing about it looks broken, which is exactly
|
|
* why it is pinned here.
|
|
*/
|
|
public function testSendMailingGoesOutOverTheMailingTransports(): void
|
|
{
|
|
$this->mailer
|
|
->expects($this->once())
|
|
->method('createAndSendEmail')
|
|
->willReturnCallback(function (array $context, array $options): void {
|
|
$this->assertSame('mailing', $options['transport']);
|
|
$this->assertSame('mailing', $options['bus_transport']);
|
|
})
|
|
;
|
|
|
|
$this->service->sendMailing($this->createMailing(
|
|
'Betreff',
|
|
'Nachricht',
|
|
$this->createRecipient('Anna', 'Berg', '[email protected]')
|
|
));
|
|
}
|
|
|
|
public function testSendMailingWithoutRecipientsSendsNothing(): void
|
|
{
|
|
$this->mailer
|
|
->expects($this->never())
|
|
->method('createAndSendEmail')
|
|
;
|
|
|
|
$this->service->sendMailing($this->createMailing('Betreff', 'Nachricht'));
|
|
}
|
|
|
|
/**
|
|
* A preview has to stay on the default relay and the default queue, or composing a
|
|
* mailing would wait on the same worker the mailing itself is queued behind.
|
|
*/
|
|
public function testPreviewLeavesTheTransportsAlone(): void
|
|
{
|
|
$this->mailer
|
|
->expects($this->once())
|
|
->method('createAndSendEmail')
|
|
->willReturnCallback(function (array $context, array $options): void {
|
|
$this->assertNull($options['transport']);
|
|
$this->assertNull($options['bus_transport']);
|
|
})
|
|
;
|
|
|
|
$mailingDto = (new TeamerMailingDto())
|
|
->setSubject('Hallo {{vorname}}')
|
|
->setMessage('Servus {{name}}')
|
|
;
|
|
|
|
$this->service->sendPreview($mailingDto, (new User())->setEmail('[email protected]'));
|
|
}
|
|
|
|
public function testMailingSnapshotSkipsRecipientsWithoutAnAddress(): void
|
|
{
|
|
$recipients = (new TeamerMailingRecipientsDto())
|
|
->addEligible($this->createRecipient('Anna', 'Berg', '[email protected]'))
|
|
->addEligible($this->createRecipient('Dana', 'Elf', null))
|
|
;
|
|
|
|
$mailing = SendTeamerMailing::fromRecipients('Betreff', 'Nachricht', $recipients);
|
|
|
|
$this->assertSame(1, $mailing->getRecipientCount());
|
|
$this->assertSame('[email protected]', $mailing->getRecipients()[0]['email']);
|
|
$this->assertSame('Anna Berg', $mailing->getRecipients()[0]['fullName']);
|
|
}
|
|
|
|
public function testSendPreviewUsesAdminOwnNameAndMarksTheSubject(): void
|
|
{
|
|
$admin = (new User())
|
|
->setEmail('[email protected]')
|
|
->setFirstName('Rita')
|
|
->setLastName('Kern')
|
|
;
|
|
|
|
$this->mailer
|
|
->expects($this->once())
|
|
->method('createAndSendEmail')
|
|
->with(
|
|
['message' => 'Servus Rita Kern'],
|
|
$this->callback(function (array $options): bool {
|
|
$this->assertSame('[email protected]', $options['to']);
|
|
$this->assertSame('[Vorschau] Hallo Rita', $options['subject']);
|
|
|
|
return true;
|
|
})
|
|
)
|
|
;
|
|
|
|
$mailingDto = (new TeamerMailingDto())
|
|
->setSubject('Hallo {{vorname}}')
|
|
->setMessage('Servus {{name}}')
|
|
;
|
|
|
|
$this->service->sendPreview($mailingDto, $admin);
|
|
}
|
|
|
|
public function testSendPreviewPrefersTheAdminsOwnTeamerRecord(): void
|
|
{
|
|
$teamer = (new Teamer())->setFirstName('Rita')->setLastName('Kern-Teamer');
|
|
$admin = (new User())
|
|
->setEmail('[email protected]')
|
|
->setFirstName('Rita')
|
|
->setLastName('Kern-User')
|
|
->setTeamer($teamer)
|
|
;
|
|
|
|
$this->assertSame(
|
|
'Kern-Teamer',
|
|
$this->service->placeholderValuesForUser($admin)[TeamerMailingService::PLACEHOLDER_LAST_NAME]
|
|
);
|
|
}
|
|
|
|
public function testPreviewFallsBackToTheAddressWhenTheAdminHasNoName(): void
|
|
{
|
|
$admin = (new User())->setEmail('[email protected]');
|
|
|
|
$values = $this->service->placeholderValuesForUser($admin);
|
|
|
|
$this->assertSame('rita.kern', $values[TeamerMailingService::PLACEHOLDER_FIRST_NAME]);
|
|
$this->assertSame('rita.kern', $values[TeamerMailingService::PLACEHOLDER_FULL_NAME]);
|
|
}
|
|
|
|
private function createMailing(string $subject, string $message, TeamerMailingRecipient ...$recipients): SendTeamerMailing
|
|
{
|
|
$dto = new TeamerMailingRecipientsDto();
|
|
|
|
foreach ($recipients as $recipient) {
|
|
$dto->addEligible($recipient);
|
|
}
|
|
|
|
return SendTeamerMailing::fromRecipients($subject, $message, $dto);
|
|
}
|
|
|
|
private function createRecipient(
|
|
string $firstName,
|
|
string $lastName,
|
|
?string $email,
|
|
bool $deleted = false,
|
|
bool $disabled = false,
|
|
): TeamerMailingRecipient {
|
|
$now = new \DateTimeImmutable();
|
|
|
|
return new TeamerMailingRecipient(
|
|
self::$nextId++,
|
|
$firstName,
|
|
$lastName,
|
|
$email,
|
|
$deleted ? $now : null,
|
|
$disabled ? $now : null
|
|
);
|
|
}
|
|
}
|