Files
myep-team/tests/Service/Teamer/TeamerMailingServiceTest.php
T

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
);
}
}