feat: notify configured recipients when something is booked
This commit is contained in:
1 parent
be37fc81f6
commit
d287cb2600
23 files changed
+1014
-1
No files matched your search
@@ -8,6 +8,7 @@ use App\Entity\Booking;
|
||||
use App\Entity\BookingSeries;
|
||||
use App\Entity\Resource;
|
||||
use App\Entity\ResourceGroup;
|
||||
use App\Entity\ResourceTypeSettings;
|
||||
use App\Entity\User;
|
||||
use App\Enum\RecurrenceRule;
|
||||
use App\Enum\ResourceType;
|
||||
@@ -48,6 +49,9 @@ class ResourceAdminControllerTest extends WebTestCase
|
||||
|
||||
$this->client->request('GET', '/admin/resources/new/desk', server: ['HTTP_HX-Request' => 'true']);
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN);
|
||||
|
||||
$this->client->request('GET', '/admin/resource-types/desk/notifications', server: ['HTTP_HX-Request' => 'true']);
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
public function testTheListShowsRoomsAndRetiredResources(): void
|
||||
@@ -293,6 +297,52 @@ class ResourceAdminControllerTest extends WebTestCase
|
||||
*
|
||||
* @param array<string, string> $fields
|
||||
*/
|
||||
public function testNotificationRecipientsAreSavedForAKind(): void
|
||||
{
|
||||
$this->client->loginUser($this->admin());
|
||||
|
||||
$response = $this->submit('/admin/resource-types/desk/notifications', 'resource_type_settings_form', [
|
||||
// Pasted from a mail client, separators and all: the form is meant to cope.
|
||||
'notificationRecipients' => "[email protected], [email protected]\n\n",
|
||||
]);
|
||||
|
||||
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||
$settings = $this->em->getRepository(ResourceTypeSettings::class)->findOneBy(['type' => ResourceType::DESK]);
|
||||
self::assertNotNull($settings);
|
||||
self::assertSame(['[email protected]', '[email protected]'], $settings->getNotificationRecipients());
|
||||
}
|
||||
|
||||
public function testAnAddressThatIsNotOneIsRefused(): void
|
||||
{
|
||||
$this->client->loginUser($this->admin());
|
||||
|
||||
$response = $this->submit('/admin/resource-types/desk/notifications', 'resource_type_settings_form', [
|
||||
'notificationRecipients' => '[email protected] kein-komma-vergessen',
|
||||
]);
|
||||
|
||||
self::assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode());
|
||||
self::assertNull($this->em->getRepository(ResourceTypeSettings::class)->findOneBy(['type' => ResourceType::DESK]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Emptying the field is how notifications are switched off again.
|
||||
*/
|
||||
public function testRecipientsCanBeClearedAgain(): void
|
||||
{
|
||||
$settings = new ResourceTypeSettings(ResourceType::DESK);
|
||||
$settings->setNotificationRecipients(['[email protected]']);
|
||||
$this->em->persist($settings);
|
||||
$this->em->flush();
|
||||
$this->client->loginUser($this->admin());
|
||||
|
||||
$this->submit('/admin/resource-types/desk/notifications', 'resource_type_settings_form', ['notificationRecipients' => '']);
|
||||
|
||||
$this->em->clear();
|
||||
$fresh = $this->em->getRepository(ResourceTypeSettings::class)->findOneBy(['type' => ResourceType::DESK]);
|
||||
self::assertNotNull($fresh);
|
||||
self::assertSame([], $fresh->getNotificationRecipients());
|
||||
}
|
||||
|
||||
private function submit(string $uri, string $formName, array $fields): Response
|
||||
{
|
||||
$crawler = $this->client->request('GET', $uri, server: ['HTTP_HX-Request' => 'true']);
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace App\Tests\Service;
|
||||
use App\Entity\Booking;
|
||||
use App\Entity\BookingSeries;
|
||||
use App\Entity\Resource;
|
||||
use App\Entity\ResourceTypeSettings;
|
||||
use App\Entity\User;
|
||||
use App\Enum\RecurrenceRule;
|
||||
use App\Enum\ResourceType;
|
||||
@@ -15,6 +16,7 @@ use App\Model\TimeRange;
|
||||
use App\Service\BookingManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\MailerAssertionsTrait;
|
||||
|
||||
/**
|
||||
* The double-booking guard now lives here rather than in a unique index, so this is where it is
|
||||
@@ -24,6 +26,8 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
*/
|
||||
class BookingManagerTest extends KernelTestCase
|
||||
{
|
||||
use MailerAssertionsTrait;
|
||||
|
||||
private EntityManagerInterface $em;
|
||||
private BookingManager $manager;
|
||||
|
||||
@@ -292,6 +296,89 @@ class BookingManagerTest extends KernelTestCase
|
||||
self::assertSame('2026-10-14', $series->getEndDate()->format('Y-m-d'));
|
||||
}
|
||||
|
||||
public function testABookingNotifiesTheRecipientsOfItsType(): void
|
||||
{
|
||||
$this->recipientsFor(ResourceType::DESK, ['[email protected]', '[email protected]']);
|
||||
|
||||
$this->manager->book($this->user('[email protected]'), $this->resource('D1'), $this->date(), TimeRange::fromMinutes(540, 780));
|
||||
|
||||
// One mail each, addressed to them alone: no recipient learns who else is on the list.
|
||||
self::assertEmailCount(2);
|
||||
$emails = self::getMailerMessages();
|
||||
self::assertSame(
|
||||
['[email protected]', '[email protected]'],
|
||||
array_map(static fn ($email): string => $email->getTo()[0]->getAddress(), $emails),
|
||||
);
|
||||
|
||||
foreach ($emails as $email) {
|
||||
self::assertCount(1, $email->getTo());
|
||||
self::assertSame([], $email->getBcc());
|
||||
self::assertSame('Neue Buchung: D1 am 01.10.2026', $email->getSubject());
|
||||
self::assertStringContainsString('D1', $email->getTextBody());
|
||||
// Both parts render: the MyE&P layout, and the text fallback beside it.
|
||||
self::assertStringContainsString('D1', $email->getHtmlBody());
|
||||
self::assertStringContainsString('Neue Buchung', $email->getHtmlBody());
|
||||
// The logo resolved through the @images Twig path and was embedded, not hotlinked.
|
||||
self::assertStringContainsString('cid:', $email->getHtmlBody());
|
||||
}
|
||||
}
|
||||
|
||||
public function testAKindWithNoRecipientsNotifiesNobody(): void
|
||||
{
|
||||
$this->recipientsFor(ResourceType::PARKING, ['[email protected]']);
|
||||
|
||||
$this->manager->book($this->user('[email protected]'), $this->resource('D1'), $this->date());
|
||||
|
||||
self::assertEmailCount(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* One mail per recipient for the series rather than one per occurrence -- four Mondays and two
|
||||
* recipients make two mails, and a yearly series would otherwise be a hundred and four.
|
||||
*/
|
||||
public function testASeriesSendsOneMailPerRecipientNotPerOccurrence(): void
|
||||
{
|
||||
$this->recipientsFor(ResourceType::DESK, ['[email protected]', '[email protected]']);
|
||||
|
||||
$result = $this->manager->bookSeries($this->weeklySeries());
|
||||
|
||||
self::assertSame(4, $result->createdCount());
|
||||
self::assertEmailCount(2);
|
||||
self::assertStringContainsString('4', (string) self::getMailerMessage()?->getTextBody());
|
||||
}
|
||||
|
||||
public function testASeriesThatBookedNothingNotifiesNobody(): void
|
||||
{
|
||||
$this->recipientsFor(ResourceType::DESK, ['[email protected]']);
|
||||
$desk = $this->resource('D1');
|
||||
$this->manager->book($this->user('[email protected]'), $desk, new \DateTimeImmutable('2026-10-05'));
|
||||
|
||||
$result = $this->manager->bookSeries(new BookingSeries(
|
||||
$this->user('[email protected]'),
|
||||
$desk,
|
||||
RecurrenceRule::WEEKLY,
|
||||
new \DateTimeImmutable('2026-10-05'),
|
||||
new \DateTimeImmutable('2026-10-09'),
|
||||
TimeRange::fromMinutes(540, 720),
|
||||
));
|
||||
|
||||
self::assertTrue($result->isCompletelyBlocked());
|
||||
// The one booking above is the only mail-worthy event, and it happened before recipients
|
||||
// could matter for the series -- so exactly one mail, for that booking.
|
||||
self::assertEmailCount(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $recipients
|
||||
*/
|
||||
private function recipientsFor(ResourceType $type, array $recipients): void
|
||||
{
|
||||
$settings = new ResourceTypeSettings($type);
|
||||
$settings->setNotificationRecipients($recipients);
|
||||
$this->em->persist($settings);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
private function weeklySeries(): BookingSeries
|
||||
{
|
||||
return new BookingSeries(
|
||||
@@ -334,6 +421,7 @@ class BookingManagerTest extends KernelTestCase
|
||||
$connection->executeStatement('DELETE FROM booking');
|
||||
$connection->executeStatement('DELETE FROM booking_series');
|
||||
$connection->executeStatement('DELETE FROM resource');
|
||||
$connection->executeStatement('DELETE FROM resource_type_settings');
|
||||
$connection->executeStatement('DELETE FROM `user`');
|
||||
$this->em->clear();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user