Files
myep/tests/MessageHandler/RoleNominationHandlerTest.php
T

129 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\MessageHandler;
use App\Email\Mailer;
use App\Entity\User;
use App\Message\RoleNominationMessage;
use App\MessageHandler\RoleNominationHandler;
use App\Repository\UserRepository;
use App\Security\Role;
use App\Service\RoleApprovalUrlGenerator;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* The notification goes to the people who can act on it, and to nobody else: an account merely
* nominated for ROLE_ADMIN must not be told about other people's nominations.
*/
class RoleNominationHandlerTest extends TestCase
{
/** @var array<string, mixed>|null */
private ?array $sentOptions = null;
/** @var array<string, mixed>|null */
private ?array $sentContext = null;
private ?string $generatedRoute = null;
private ?int $generatedReferenceType = null;
public function testMailsEveryAdministrator(): void
{
$handler = $this->handler(
$this->user(7, '[email protected]'),
[
$this->user(1, '[email protected]', [Role::ADMIN]),
$this->user(2, '[email protected]', [Role::ADMIN]),
],
);
$handler(new RoleNominationMessage(7, [Role::ADMIN]));
self::assertSame(['[email protected]', '[email protected]'], $this->sentOptions['to']);
self::assertSame('email/role_nomination.html.twig', $this->sentOptions['template']);
// Labelled for a human, not the raw role string.
self::assertSame(['Administration'], $this->sentContext['roles']);
// The filtered user list, not app_admin_user_permissions: that route renders a bare htmx
// fragment, which a mail client following the link would show unstyled.
self::assertSame('app_admin_user', $this->generatedRoute);
// Absolute, because a worker has no request to borrow a host from.
self::assertSame(UrlGeneratorInterface::ABSOLUTE_URL, $this->generatedReferenceType);
self::assertSame('https://my.ep-reisen.de/generated', $this->sentContext['approvalUrl']);
}
public function testAMissingAccountIsANoOp(): void
{
$handler = $this->handler(null, [$this->user(1, '[email protected]', [Role::ADMIN])]);
$handler(new RoleNominationMessage(7, [Role::ADMIN]));
self::assertNull($this->sentOptions);
}
public function testWithoutAnAdministratorNothingIsSent(): void
{
$handler = $this->handler($this->user(7, '[email protected]'), []);
$handler(new RoleNominationMessage(7, [Role::ADMIN]));
self::assertNull($this->sentOptions);
}
/**
* @param string[] $roles
*/
private function user(int $id, string $email, array $roles = []): User
{
$user = (new User($email))->setRoles($roles);
// The id is generated by Doctrine and has no setter, but the notification links to it.
$property = new \ReflectionProperty(User::class, 'id');
$property->setValue($user, $id);
return $user;
}
/**
* @param User[] $administrators
*/
private function handler(?User $nominee, array $administrators): RoleNominationHandler
{
$userRepository = $this->createStub(UserRepository::class);
$userRepository->method('find')->willReturn($nominee);
$userRepository->method('findAdministrators')->willReturn($administrators);
$mailer = $this->createStub(Mailer::class);
$mailer
->method('createAndSendEmail')
->willReturnCallback(function (array $context, array $options): void {
$this->sentContext = $context;
$this->sentOptions = $options;
})
;
// Records what was asked of the router rather than imitating its output: what matters is
// which route the mail points at and that it is absolute, not how a query string is escaped.
$urlGenerator = $this->createStub(UrlGeneratorInterface::class);
$urlGenerator
->method('generate')
->willReturnCallback(function (string $route, array $parameters, int $referenceType): string {
$this->generatedRoute = $route;
$this->generatedReferenceType = $referenceType;
return 'https://my.ep-reisen.de/generated';
})
;
return new RoleNominationHandler(
$userRepository,
$mailer,
new RoleApprovalUrlGenerator($urlGenerator),
$this->createStub(LoggerInterface::class),
);
}
}