270 lines
8.7 KiB
PHP
270 lines
8.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Email;
|
|
|
|
use App\Config\EmailTextCatalog;
|
|
use App\Config\EmailTextKey;
|
|
use App\Email\EmailTextRenderer;
|
|
use App\Entity\EmailText;
|
|
use App\Repository\EmailTextRepository;
|
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|
|
|
/**
|
|
* The renderer is the only thing standing between text an admin (or a teamer) typed and
|
|
* the HTML of a mail, so most of what is asserted here is that nothing but the renderer
|
|
* itself can introduce a tag.
|
|
*/
|
|
class EmailTextRendererTest extends KernelTestCase
|
|
{
|
|
public function testTheStoredWordingWinsOverTheDefault(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(subject: 'Neuer Betreff', body: 'Neuer Text'),
|
|
['destination' => 'Skireise']
|
|
);
|
|
|
|
$this->assertSame('Neuer Betreff', $rendered->subject);
|
|
$this->assertSame('<p>Neuer Text</p>', $rendered->bodyHtml);
|
|
}
|
|
|
|
/**
|
|
* A fresh database has no rows at all, so the delivered wording has to carry the mails
|
|
* on its own.
|
|
*/
|
|
public function testTheDefaultWordingIsUsedWithoutAStoredRow(): void
|
|
{
|
|
$rendered = $this->render(null, ['destination' => 'Skireise']);
|
|
|
|
$this->assertSame('Deine Bewerbung wurde abgelehnt', $rendered->subject);
|
|
$this->assertStringContainsString('Skireise', $rendered->bodyHtml);
|
|
}
|
|
|
|
public function testMarkupTypedByAnAdminIsEscaped(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(body: 'Hallo <script>alert(1)</script>'),
|
|
['destination' => 'Skireise']
|
|
);
|
|
|
|
$this->assertStringNotContainsString('<script>', $rendered->bodyHtml);
|
|
$this->assertStringContainsString('<script>', $rendered->bodyHtml);
|
|
}
|
|
|
|
/**
|
|
* destination, reason and comment are all typed by people, not by the application.
|
|
*/
|
|
public function testMarkupInsideAPlaceholderValueIsEscaped(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(body: 'Einsatz {destination}'),
|
|
['destination' => '<img src=x onerror=alert(1)>']
|
|
);
|
|
|
|
$this->assertStringNotContainsString('<img', $rendered->bodyHtml);
|
|
$this->assertStringContainsString('<img', $rendered->bodyHtml);
|
|
}
|
|
|
|
public function testAsterisksMakeTextBold(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(body: 'Das ist *wichtig* und das nicht'),
|
|
['destination' => 'Skireise']
|
|
);
|
|
|
|
$this->assertSame('<p>Das ist <strong>wichtig</strong> und das nicht</p>', $rendered->bodyHtml);
|
|
}
|
|
|
|
public function testAnEscapedAsteriskStaysLiteral(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(body: 'Ein Sternchen: \*'),
|
|
['destination' => 'Skireise']
|
|
);
|
|
|
|
$this->assertSame('<p>Ein Sternchen: *</p>', $rendered->bodyHtml);
|
|
}
|
|
|
|
/**
|
|
* Emphasis is the admin's to give. If a teamer could hand one over in a rejection
|
|
* comment, the substitution order would be wrong and escaping would be next.
|
|
*/
|
|
public function testAsterisksInsideAPlaceholderValueStayLiteral(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(body: 'Einsatz {destination}'),
|
|
['destination' => '*nicht fett*']
|
|
);
|
|
|
|
$this->assertStringNotContainsString('<strong>', $rendered->bodyHtml);
|
|
$this->assertStringContainsString('*nicht fett*', $rendered->bodyHtml);
|
|
}
|
|
|
|
public function testAnEmptyPlaceholderFallsBackToADash(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(body: 'Absprachen: {destination}'),
|
|
['destination' => ' ']
|
|
);
|
|
|
|
$this->assertSame('<p>Absprachen: -</p>', $rendered->bodyHtml);
|
|
}
|
|
|
|
/**
|
|
* A caller that forgets a placeholder should print the same dash, not the raw token.
|
|
*/
|
|
public function testAMissingPlaceholderFallsBackToADash(): void
|
|
{
|
|
$rendered = $this->render($this->emailText(body: 'Absprachen: {destination}'), []);
|
|
|
|
$this->assertSame('<p>Absprachen: -</p>', $rendered->bodyHtml);
|
|
}
|
|
|
|
public function testBlankLinesBecomeParagraphsAndSingleBreaksStay(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(body: "Erste Zeile\nZweite Zeile\n\nNeuer Absatz"),
|
|
['destination' => 'Skireise']
|
|
);
|
|
|
|
$this->assertSame(
|
|
"<p>Erste Zeile<br />\nZweite Zeile</p>\n<p>Neuer Absatz</p>",
|
|
$rendered->bodyHtml
|
|
);
|
|
}
|
|
|
|
/**
|
|
* A rejection comment arrives as one multi-line value inside a single paragraph.
|
|
*/
|
|
public function testLineBreaksInsideAPlaceholderValueSurvive(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(body: '{destination}'),
|
|
['destination' => "Erste Zeile\nZweite Zeile"]
|
|
);
|
|
|
|
$this->assertSame("<p>Erste Zeile<br />\nZweite Zeile</p>", $rendered->bodyHtml);
|
|
}
|
|
|
|
public function testMailAddressesAndUrlsBecomeLinks(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(body: 'Schreib an [email protected] oder https://ep-reisen.de/team.'),
|
|
['destination' => 'Skireise']
|
|
);
|
|
|
|
$this->assertStringContainsString(
|
|
'<a href="mailto:[email protected]">[email protected]</a>',
|
|
$rendered->bodyHtml
|
|
);
|
|
// The full stop ends the sentence, it is not part of the address.
|
|
$this->assertStringContainsString(
|
|
'<a href="https://ep-reisen.de/team">https://ep-reisen.de/team</a>.',
|
|
$rendered->bodyHtml
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Mail clients show the subject as plain text, so escaping it would leak entities
|
|
* into the inbox.
|
|
*/
|
|
public function testTheSubjectIsPlainText(): void
|
|
{
|
|
$rendered = $this->render(
|
|
$this->emailText(subject: 'Einsatz {destination}'),
|
|
['destination' => 'Ski & Snowboard']
|
|
);
|
|
|
|
$this->assertSame('Einsatz Ski & Snowboard', $rendered->subject);
|
|
}
|
|
|
|
/**
|
|
* The admin preview renders what is in the form, not what is in the database - a
|
|
* preview showing the stored wording while the admin types a new one would be
|
|
* pointless.
|
|
*/
|
|
public function testGivenValuesAreRenderedInsteadOfTheStoredOnes(): void
|
|
{
|
|
$repository = $this->createMock(EmailTextRepository::class);
|
|
$repository
|
|
->method('findByKey')
|
|
->willReturn($this->emailText(subject: 'Gespeichert', body: 'Gespeicherter Text'))
|
|
;
|
|
|
|
self::bootKernel();
|
|
|
|
$renderer = new EmailTextRenderer(
|
|
self::getContainer()->get(EmailTextCatalog::class),
|
|
$repository
|
|
);
|
|
|
|
$rendered = $renderer->renderValues(
|
|
EmailTextKey::APPLICATION_REJECTED,
|
|
'Entwurf {destination}',
|
|
'Überschrift',
|
|
'Entwurfstext *fett*',
|
|
['destination' => 'Skireise']
|
|
);
|
|
|
|
$this->assertSame('Entwurf Skireise', $rendered->subject);
|
|
$this->assertSame('Überschrift', $rendered->headline);
|
|
$this->assertSame('<p>Entwurfstext <strong>fett</strong></p>', $rendered->bodyHtml);
|
|
}
|
|
|
|
/**
|
|
* A preview is asked for while the form is still half filled.
|
|
*/
|
|
public function testEmptyDraftValuesRenderWithoutFallingBackToTheDefault(): void
|
|
{
|
|
self::bootKernel();
|
|
|
|
$renderer = new EmailTextRenderer(
|
|
self::getContainer()->get(EmailTextCatalog::class),
|
|
$this->createMock(EmailTextRepository::class)
|
|
);
|
|
|
|
$rendered = $renderer->renderValues(
|
|
EmailTextKey::APPLICATION_REJECTED,
|
|
null,
|
|
null,
|
|
null,
|
|
['destination' => 'Skireise']
|
|
);
|
|
|
|
$this->assertSame('', $rendered->subject);
|
|
$this->assertSame('', $rendered->headline);
|
|
$this->assertSame('', $rendered->bodyHtml);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string|int|null> $placeholders
|
|
*/
|
|
private function render(?EmailText $emailText, array $placeholders): \App\Model\RenderedEmailTextDto
|
|
{
|
|
$repository = $this->createMock(EmailTextRepository::class);
|
|
$repository
|
|
->method('findByKey')
|
|
->willReturn($emailText)
|
|
;
|
|
|
|
self::bootKernel();
|
|
|
|
$renderer = new EmailTextRenderer(
|
|
self::getContainer()->get(EmailTextCatalog::class),
|
|
$repository
|
|
);
|
|
|
|
return $renderer->render(EmailTextKey::APPLICATION_REJECTED, $placeholders);
|
|
}
|
|
|
|
private function emailText(string $subject = 'Betreff', string $body = 'Text'): EmailText
|
|
{
|
|
return (new EmailText(EmailTextKey::APPLICATION_REJECTED))
|
|
->setSubject($subject)
|
|
->setBody($body)
|
|
->setHeadline('')
|
|
;
|
|
}
|
|
}
|