feat: let admins notify all users about updated access codes
This commit is contained in:
1 parent
a957e0d662
commit
d68b6b156c
16 files changed
+458
-59
No files matched your search
@@ -56,6 +56,10 @@ class AccessCodeAdminControllerTest extends WebTestCase
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN);
|
||||
$this->em->clear();
|
||||
self::assertNotNull($this->em->find(AccessCode::class, $code->getId()));
|
||||
|
||||
$this->client->request('POST', '/admin/access-codes/notify', ['_token' => 'whatever'], server: ['HTTP_HX-Request' => 'true']);
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN);
|
||||
self::assertEmailCount(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,6 +223,59 @@ class AccessCodeAdminControllerTest extends WebTestCase
|
||||
self::assertNotNull($this->em->find(AccessCode::class, $code->getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody who can sign in gets a mail of their own, and none of them contains a code -- not
|
||||
* even one only the recipient's own role could read.
|
||||
*/
|
||||
public function testNotifyingTheStaffMailsEveryActiveUserWithoutTheCodes(): void
|
||||
{
|
||||
$this->code('Aufzug', '4711');
|
||||
$this->code('Tresor', '9876');
|
||||
$admin = $this->admin();
|
||||
$this->user('[email protected]');
|
||||
$locked = $this->user('[email protected]');
|
||||
$locked->disable('ausgeschieden');
|
||||
$this->em->flush();
|
||||
$expected = $this->em->getRepository(User::class)->count(['disabledAt' => null]);
|
||||
$this->client->loginUser($admin);
|
||||
$uri = '/admin/access-codes/notify';
|
||||
|
||||
$modal = $this->client->request('GET', $uri, server: ['HTTP_HX-Request' => 'true']);
|
||||
self::assertStringContainsString(\sprintf('%d Mitarbeitende', $expected), $modal->text());
|
||||
|
||||
$this->client->request('POST', $uri, ['_token' => $this->confirmationToken($uri)], server: ['HTTP_HX-Request' => 'true']);
|
||||
|
||||
self::assertSame('/admin/access-codes', $this->client->getResponse()->headers->get('HX-Redirect'));
|
||||
self::assertEmailCount($expected);
|
||||
|
||||
$recipients = [];
|
||||
foreach (self::getMailerMessages() as $email) {
|
||||
self::assertCount(1, $email->getTo());
|
||||
$recipients[] = $email->getTo()[0]->getAddress();
|
||||
|
||||
foreach ([$email->getTextBody(), $email->getHtmlBody()] as $body) {
|
||||
self::assertStringContainsString('https://localhost/access-codes', $body);
|
||||
self::assertStringNotContainsString('4711', $body);
|
||||
self::assertStringNotContainsString('9876', $body);
|
||||
self::assertStringNotContainsString('Aufzug', $body);
|
||||
}
|
||||
}
|
||||
|
||||
self::assertContains('[email protected]', $recipients);
|
||||
self::assertContains('[email protected]', $recipients);
|
||||
self::assertNotContains('[email protected]', $recipients);
|
||||
}
|
||||
|
||||
public function testNotifyingTheStaffNeedsAValidToken(): void
|
||||
{
|
||||
$this->client->loginUser($this->admin());
|
||||
|
||||
$this->client->request('POST', '/admin/access-codes/notify', ['_token' => 'whatever'], server: ['HTTP_HX-Request' => 'true']);
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN);
|
||||
self::assertEmailCount(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the modal, then posts the given fields with its CSRF token -- the round trip a
|
||||
* browser makes.
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Email;
|
||||
|
||||
use App\Email\Mailer;
|
||||
use Monolog\Handler\TestHandler;
|
||||
use Monolog\Logger;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Component\Mailer\Envelope;
|
||||
use Symfony\Component\Mailer\Exception\TransportException;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
/**
|
||||
* The two promises every caller relies on: one mail per recipient, addressed to them alone, and
|
||||
* a failure that costs only its own recipient and is never thrown.
|
||||
*/
|
||||
class MailerTest extends TestCase
|
||||
{
|
||||
public function testEveryRecipientGetsTheirOwnMail(): void
|
||||
{
|
||||
$transport = $this->transport();
|
||||
$mailer = new Mailer($transport, new Logger('test'), '[email protected]');
|
||||
|
||||
$report = $mailer->send(
|
||||
['[email protected]', new Address('[email protected]', 'B')],
|
||||
'Betreff',
|
||||
'email/some',
|
||||
['key' => 'value'],
|
||||
);
|
||||
|
||||
self::assertSame(2, $report->sent);
|
||||
self::assertSame(0, $report->failed);
|
||||
self::assertCount(2, $transport->sent);
|
||||
|
||||
foreach ($transport->sent as $i => $email) {
|
||||
self::assertCount(1, $email->getTo());
|
||||
self::assertSame(['[email protected]', '[email protected]'][$i], $email->getTo()[0]->getAddress());
|
||||
self::assertSame('[email protected]', $email->getFrom()[0]->getAddress());
|
||||
self::assertSame('E&P Platz', $email->getFrom()[0]->getName());
|
||||
self::assertSame('Betreff', $email->getSubject());
|
||||
self::assertSame('email/some.html.twig', $email->getHtmlTemplate());
|
||||
self::assertSame('email/some.txt.twig', $email->getTextTemplate());
|
||||
self::assertSame(['key' => 'value'], $email->getContext());
|
||||
}
|
||||
}
|
||||
|
||||
public function testAFailedRecipientIsLoggedAndTheOthersStillGetTheirs(): void
|
||||
{
|
||||
$transport = $this->transport(failFor: '[email protected]');
|
||||
$log = new TestHandler();
|
||||
$mailer = new Mailer($transport, new Logger('test', [$log]), '[email protected]');
|
||||
|
||||
$report = $mailer->send(['[email protected]', '[email protected]', '[email protected]'], 'Betreff', 'email/some');
|
||||
|
||||
self::assertSame(2, $report->sent);
|
||||
self::assertSame(1, $report->failed);
|
||||
self::assertCount(2, $transport->sent);
|
||||
self::assertTrue($log->hasErrorThatContains('Email could not be sent.'));
|
||||
self::assertSame('[email protected]', $log->getRecords()[0]->context['recipient']);
|
||||
}
|
||||
|
||||
public function testNoRecipientsSendsNothing(): void
|
||||
{
|
||||
$transport = $this->transport();
|
||||
$mailer = new Mailer($transport, new Logger('test'), '[email protected]');
|
||||
|
||||
$report = $mailer->send([], 'Betreff', 'email/some');
|
||||
|
||||
self::assertSame(0, $report->sent);
|
||||
self::assertSame([], $transport->sent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MailerInterface&object{sent: list<TemplatedEmail>}
|
||||
*/
|
||||
private function transport(?string $failFor = null): MailerInterface
|
||||
{
|
||||
return new class($failFor) implements MailerInterface {
|
||||
/** @var list<TemplatedEmail> */
|
||||
public array $sent = [];
|
||||
|
||||
public function __construct(private readonly ?string $failFor)
|
||||
{
|
||||
}
|
||||
|
||||
public function send(RawMessage $message, ?Envelope $envelope = null): void
|
||||
{
|
||||
\assert($message instanceof TemplatedEmail);
|
||||
|
||||
if ($message->getTo()[0]->getAddress() === $this->failFor) {
|
||||
throw new TransportException('550 mailbox unavailable');
|
||||
}
|
||||
|
||||
$this->sent[] = $message;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -180,6 +180,14 @@ class AuditLoggerTest extends TestCase
|
||||
self::assertSame(7, $records[2]->context['access_code_id']);
|
||||
}
|
||||
|
||||
public function testAnAnnouncementIsRecordedWithItsCounts(): void
|
||||
{
|
||||
$this->audit->accessCodesAnnounced(50, 1);
|
||||
|
||||
self::assertSame('Access code update announced.', $this->record()->message);
|
||||
self::assertSame(['sent' => 50, 'failed' => 1], $this->record()->context);
|
||||
}
|
||||
|
||||
private function record(): LogRecord
|
||||
{
|
||||
return $this->handler->getRecords()[0];
|
||||
|
||||
Reference in new issue
Block a user