wip: guest account registration (self-service and booking create flow)
This commit is contained in:
1 parent
e4430ac5c5
commit
d74888f64b
43 files changed
+2736
-144
No files matched your search
@@ -0,0 +1,623 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\BpnConnect\BpnConnectClient;
|
||||
use App\BpnConnect\Model\BookingLookupResult;
|
||||
use App\Email\Mailer;
|
||||
use App\Entity\User;
|
||||
use App\Exception\GuestAccountActivationExpiredException;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Service\BookingGuestAccountManager;
|
||||
use App\Service\UserBootstrapper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\UriSigner;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Mailer\Exception\TransportException;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class BookingGuestAccountManagerTest extends TestCase
|
||||
{
|
||||
public function testProvisionGuestAccountCreatesCustomerAndSendsActivationMail(): void
|
||||
{
|
||||
$persistedUser = null;
|
||||
$plainPassword = null;
|
||||
$generatedRoute = null;
|
||||
$generatedParameters = null;
|
||||
$generatedReferenceType = null;
|
||||
$capturedContext = null;
|
||||
$capturedOptions = null;
|
||||
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$userRepository
|
||||
->expects(self::once())
|
||||
->method('findOneByNormalizedEmail')
|
||||
->with('[email protected]')
|
||||
->willReturn(null);
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager
|
||||
->expects(self::once())
|
||||
->method('persist')
|
||||
->with(self::callback(static function (User $user) use (&$persistedUser): bool {
|
||||
$persistedUser = $user;
|
||||
|
||||
return true;
|
||||
}));
|
||||
$entityManager
|
||||
->expects(self::once())
|
||||
->method('flush');
|
||||
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$passwordHasher
|
||||
->expects(self::once())
|
||||
->method('hashPassword')
|
||||
->with(self::isInstanceOf(User::class), self::callback(static function (string $password) use (&$plainPassword): bool {
|
||||
$plainPassword = $password;
|
||||
|
||||
return '' !== $password;
|
||||
}))
|
||||
->willReturn('hashed-password');
|
||||
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$urlGenerator
|
||||
->expects(self::once())
|
||||
->method('generate')
|
||||
->willReturnCallback(static function (string $route, array $parameters, int $referenceType) use (&$generatedRoute, &$generatedParameters, &$generatedReferenceType): string {
|
||||
$generatedRoute = $route;
|
||||
$generatedParameters = $parameters;
|
||||
$generatedReferenceType = $referenceType;
|
||||
|
||||
return 'https://example.test/welcome/activate?'.http_build_query($parameters);
|
||||
});
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer
|
||||
->expects(self::once())
|
||||
->method('createAndSendEmail')
|
||||
->willReturnCallback(static function (array $context, array $options) use (&$capturedContext, &$capturedOptions): void {
|
||||
$capturedContext = $context;
|
||||
$capturedOptions = $options;
|
||||
});
|
||||
|
||||
$manager = $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer);
|
||||
$uriSigner = new UriSigner('test-secret');
|
||||
|
||||
self::assertTrue($manager->provisionGuestAccount(' [email protected] ', 'Alice', 'Example', 12345, 67890));
|
||||
self::assertSame('app_guest_account_activate', $generatedRoute);
|
||||
self::assertSame(UrlGeneratorInterface::ABSOLUTE_URL, $generatedReferenceType);
|
||||
self::assertSame('[email protected]', $generatedParameters['email']);
|
||||
self::assertSame($persistedUser?->getWelcomeActivationNonce(), $generatedParameters['nonce']);
|
||||
self::assertTrue($uriSigner->check((string) $capturedContext['activationUrl']));
|
||||
self::assertSame('[email protected]', $capturedOptions['to']);
|
||||
self::assertSame('Dein MyE&P Konto ist bereit', $capturedOptions['subject']);
|
||||
self::assertSame('email/booking_guest_account_welcome.html.twig', $capturedOptions['template']);
|
||||
self::assertSame('Alice', $capturedContext['firstName']);
|
||||
self::assertSame('Example', $capturedContext['lastName']);
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{48}$/', (string) $plainPassword);
|
||||
self::assertInstanceOf(User::class, $persistedUser);
|
||||
self::assertSame('[email protected]', $persistedUser->getEmail());
|
||||
self::assertSame(12345, $persistedUser->getAddressId());
|
||||
self::assertSame(67890, $persistedUser->getPersonId());
|
||||
self::assertSame(['ROLE_USER', 'ROLE_CUSTOMER'], $persistedUser->getRoles());
|
||||
self::assertSame([], $persistedUser->getHotelCodes());
|
||||
self::assertTrue($persistedUser->isProfileComplete());
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{64}$/', (string) $persistedUser->getWelcomeActivationNonce());
|
||||
self::assertInstanceOf(\DateTimeImmutable::class, $persistedUser->getWelcomeActivationExpiresAt());
|
||||
self::assertNull($persistedUser->getWelcomeActivatedAt());
|
||||
}
|
||||
|
||||
public function testProvisionGuestAccountReturnsFalseWhenLocalUserAlreadyExists(): void
|
||||
{
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$userRepository
|
||||
->expects(self::once())
|
||||
->method('findOneByNormalizedEmail')
|
||||
->with('[email protected]')
|
||||
->willReturn(new User('[email protected]'));
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects(self::never())->method('persist');
|
||||
$entityManager->expects(self::never())->method('flush');
|
||||
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$passwordHasher->expects(self::never())->method('hashPassword');
|
||||
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$urlGenerator->expects(self::never())->method('generate');
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer->expects(self::never())->method('createAndSendEmail');
|
||||
|
||||
$manager = $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer);
|
||||
|
||||
self::assertFalse($manager->provisionGuestAccount('[email protected]', 'Alice', 'Example'));
|
||||
}
|
||||
|
||||
public function testRequestPortalAccessProvisionsGuestAccountWhenBookingMatches(): void
|
||||
{
|
||||
$persistedUser = null;
|
||||
$capturedContext = null;
|
||||
$capturedOptions = null;
|
||||
$booking = $this->createLookupResult('[email protected]', 123, 'Alice', 'Example');
|
||||
|
||||
$bpnConnectClient = $this->createMock(BpnConnectClient::class);
|
||||
$bpnConnectClient
|
||||
->expects(self::once())
|
||||
->method('lookupBooking')
|
||||
->with('[email protected]', 123)
|
||||
->willReturn($booking);
|
||||
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$userRepository
|
||||
->expects(self::exactly(2))
|
||||
->method('findOneByNormalizedEmail')
|
||||
->with('[email protected]')
|
||||
->willReturnOnConsecutiveCalls(null, null);
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager
|
||||
->expects(self::once())
|
||||
->method('persist')
|
||||
->with(self::callback(static function (User $user) use (&$persistedUser): bool {
|
||||
$persistedUser = $user;
|
||||
|
||||
return true;
|
||||
}));
|
||||
$entityManager->expects(self::once())->method('flush');
|
||||
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$passwordHasher
|
||||
->expects(self::once())
|
||||
->method('hashPassword')
|
||||
->willReturn('hashed-password');
|
||||
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$urlGenerator
|
||||
->expects(self::once())
|
||||
->method('generate')
|
||||
->willReturn('https://example.test/welcome/[email protected]&nonce=current&expires=9999999999');
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer
|
||||
->expects(self::once())
|
||||
->method('createAndSendEmail')
|
||||
->willReturnCallback(static function (array $context, array $options) use (&$capturedContext, &$capturedOptions): void {
|
||||
$capturedContext = $context;
|
||||
$capturedOptions = $options;
|
||||
});
|
||||
|
||||
$manager = $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer, $bpnConnectClient);
|
||||
|
||||
self::assertSame(BookingGuestAccountManager::PORTAL_ACCESS_SENT, $manager->requestPortalAccess('[email protected]', 123));
|
||||
self::assertInstanceOf(User::class, $persistedUser);
|
||||
self::assertSame('[email protected]', $persistedUser->getEmail());
|
||||
self::assertSame(12345, $persistedUser->getAddressId());
|
||||
self::assertSame(67890, $persistedUser->getPersonId());
|
||||
self::assertSame('[email protected]', $capturedOptions['to']);
|
||||
self::assertSame('Alice', $capturedContext['firstName']);
|
||||
self::assertSame('Example', $capturedContext['lastName']);
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{64}$/', (string) $persistedUser->getWelcomeActivationNonce());
|
||||
self::assertInstanceOf(\DateTimeImmutable::class, $persistedUser->getWelcomeActivationExpiresAt());
|
||||
self::assertNull($persistedUser->getWelcomeActivatedAt());
|
||||
}
|
||||
|
||||
public function testRequestPortalAccessResendsPendingInviteWhenBookingMatches(): void
|
||||
{
|
||||
$user = new User('[email protected]');
|
||||
$user->setAddressId(12345);
|
||||
$user->setPersonId(67890);
|
||||
$user->setWelcomeActivationNonce('current-nonce');
|
||||
$user->setWelcomeActivationExpiresAt(new \DateTimeImmutable('+30 minutes'));
|
||||
$booking = $this->createLookupResult('[email protected]', 123, 'Alice', 'Example');
|
||||
|
||||
$bpnConnectClient = $this->createMock(BpnConnectClient::class);
|
||||
$bpnConnectClient
|
||||
->expects(self::once())
|
||||
->method('lookupBooking')
|
||||
->with('[email protected]', 123)
|
||||
->willReturn($booking);
|
||||
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$userRepository
|
||||
->expects(self::once())
|
||||
->method('findOneByNormalizedEmail')
|
||||
->with('[email protected]')
|
||||
->willReturn($user);
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects(self::once())->method('flush');
|
||||
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$passwordHasher->expects(self::never())->method('hashPassword');
|
||||
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$urlGenerator
|
||||
->expects(self::once())
|
||||
->method('generate')
|
||||
->willReturn('https://example.test/welcome/[email protected]&nonce=new-nonce&expires=9999999999');
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer
|
||||
->expects(self::once())
|
||||
->method('createAndSendEmail');
|
||||
|
||||
$manager = $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer, $bpnConnectClient);
|
||||
|
||||
self::assertSame(BookingGuestAccountManager::PORTAL_ACCESS_SENT, $manager->requestPortalAccess('[email protected]', 123));
|
||||
self::assertNotSame('current-nonce', $user->getWelcomeActivationNonce());
|
||||
self::assertNull($user->getWelcomeActivatedAt());
|
||||
self::assertInstanceOf(\DateTimeImmutable::class, $user->getWelcomeActivationExpiresAt());
|
||||
}
|
||||
|
||||
public function testRequestPortalAccessUsesExistingUserIdsAndDoesNotMailActivatedAccount(): void
|
||||
{
|
||||
$user = new User('[email protected]');
|
||||
$user->setAddressId(12345);
|
||||
$user->setPersonId(67890);
|
||||
$user->markWelcomeActivated();
|
||||
$booking = $this->createLookupResult('[email protected]', 123, 'Alice', 'Example');
|
||||
|
||||
$bpnConnectClient = $this->createMock(BpnConnectClient::class);
|
||||
$bpnConnectClient
|
||||
->expects(self::once())
|
||||
->method('lookupBooking')
|
||||
->with('[email protected]', 123)
|
||||
->willReturn($booking);
|
||||
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$userRepository
|
||||
->expects(self::once())
|
||||
->method('findOneByNormalizedEmail')
|
||||
->with('[email protected]')
|
||||
->willReturn($user);
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects(self::never())->method('persist');
|
||||
$entityManager->expects(self::never())->method('flush');
|
||||
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$passwordHasher->expects(self::never())->method('hashPassword');
|
||||
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$urlGenerator->expects(self::never())->method('generate');
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer->expects(self::never())->method('createAndSendEmail');
|
||||
|
||||
$manager = $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer, $bpnConnectClient);
|
||||
|
||||
self::assertSame(BookingGuestAccountManager::PORTAL_ACCESS_ALREADY_ACTIVE, $manager->requestPortalAccess('[email protected]', 123));
|
||||
}
|
||||
|
||||
public function testProvisionGuestAccountForBookingFetchesBookingIdentifiers(): void
|
||||
{
|
||||
$persistedUser = null;
|
||||
$booking = $this->createLookupResult('[email protected]', 123, 'Alice', 'Example');
|
||||
|
||||
$bpnConnectClient = $this->createMock(BpnConnectClient::class);
|
||||
$bpnConnectClient
|
||||
->expects(self::once())
|
||||
->method('lookupBooking')
|
||||
->with('[email protected]', 123)
|
||||
->willReturn($booking);
|
||||
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$userRepository
|
||||
->expects(self::once())
|
||||
->method('findOneByNormalizedEmail')
|
||||
->with('[email protected]')
|
||||
->willReturn(null);
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager
|
||||
->expects(self::once())
|
||||
->method('persist')
|
||||
->with(self::callback(static function (User $user) use (&$persistedUser): bool {
|
||||
$persistedUser = $user;
|
||||
|
||||
return true;
|
||||
}));
|
||||
$entityManager->expects(self::once())->method('flush');
|
||||
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$passwordHasher
|
||||
->expects(self::once())
|
||||
->method('hashPassword')
|
||||
->willReturn('hashed-password');
|
||||
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$urlGenerator
|
||||
->expects(self::once())
|
||||
->method('generate')
|
||||
->willReturn('https://example.test/welcome/[email protected]&nonce=current&expires=9999999999');
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer
|
||||
->expects(self::once())
|
||||
->method('createAndSendEmail');
|
||||
|
||||
$manager = $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer, $bpnConnectClient);
|
||||
|
||||
self::assertTrue($manager->provisionGuestAccountForBooking('[email protected]', 123));
|
||||
self::assertInstanceOf(User::class, $persistedUser);
|
||||
self::assertSame('[email protected]', $persistedUser->getEmail());
|
||||
self::assertSame(12345, $persistedUser->getAddressId());
|
||||
self::assertSame(67890, $persistedUser->getPersonId());
|
||||
}
|
||||
|
||||
public function testRequestPortalAccessRejectsMismatchedBookingGenerically(): void
|
||||
{
|
||||
$booking = $this->createLookupResult('[email protected]', 123, 'Alice', 'Example');
|
||||
|
||||
$bpnConnectClient = $this->createMock(BpnConnectClient::class);
|
||||
$bpnConnectClient
|
||||
->expects(self::once())
|
||||
->method('lookupBooking')
|
||||
->with('[email protected]', 123)
|
||||
->willReturn($booking);
|
||||
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$userRepository
|
||||
->expects(self::once())
|
||||
->method('findOneByNormalizedEmail')
|
||||
->with('[email protected]')
|
||||
->willReturn(null);
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects(self::never())->method('persist');
|
||||
$entityManager->expects(self::never())->method('flush');
|
||||
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$passwordHasher->expects(self::never())->method('hashPassword');
|
||||
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$urlGenerator->expects(self::never())->method('generate');
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer->expects(self::never())->method('createAndSendEmail');
|
||||
|
||||
$manager = $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer, $bpnConnectClient);
|
||||
|
||||
self::assertSame(BookingGuestAccountManager::PORTAL_ACCESS_NOT_SENT, $manager->requestPortalAccess('[email protected]', 123));
|
||||
}
|
||||
|
||||
public function testConsumeActivationMarksInviteAsActivated(): void
|
||||
{
|
||||
$user = new User('[email protected]');
|
||||
$user->setWelcomeActivationNonce('current-nonce');
|
||||
$user->setWelcomeActivationExpiresAt(new \DateTimeImmutable('+30 minutes'));
|
||||
|
||||
$manager = $this->createValidationManager($user);
|
||||
$manager->consumeActivation($user);
|
||||
|
||||
self::assertNull($user->getWelcomeActivationNonce());
|
||||
self::assertNull($user->getWelcomeActivationExpiresAt());
|
||||
self::assertInstanceOf(\DateTimeImmutable::class, $user->getWelcomeActivatedAt());
|
||||
}
|
||||
|
||||
public function testValidateActivationRequestAcceptsValidSignedRequestAndRejectsReplay(): void
|
||||
{
|
||||
$user = new User('[email protected]');
|
||||
$user->setWelcomeActivationNonce('current-nonce');
|
||||
|
||||
$manager = $this->createValidationManager($user);
|
||||
$request = Request::create(
|
||||
$this->buildActivationUrl('[email protected]', 'current-nonce', time() + 3600),
|
||||
'GET'
|
||||
);
|
||||
|
||||
self::assertSame($user, $manager->validateActivationRequest($request));
|
||||
|
||||
$manager->consumeActivation($user);
|
||||
|
||||
$this->expectException(NotFoundHttpException::class);
|
||||
$manager->validateActivationRequest($request);
|
||||
}
|
||||
|
||||
public function testValidateActivationRequestRejectsExpiredLink(): void
|
||||
{
|
||||
$user = new User('[email protected]');
|
||||
$user->setWelcomeActivationNonce('current-nonce');
|
||||
|
||||
$manager = $this->createValidationManager($user);
|
||||
$request = Request::create(
|
||||
$this->buildActivationUrl('[email protected]', 'current-nonce', time() - 60),
|
||||
'GET'
|
||||
);
|
||||
|
||||
$this->expectException(GuestAccountActivationExpiredException::class);
|
||||
$manager->validateActivationRequest($request);
|
||||
}
|
||||
|
||||
public function testValidateActivationUrlForResendAcceptsExpiredLink(): void
|
||||
{
|
||||
$user = new User('[email protected]');
|
||||
$user->setWelcomeActivationNonce('current-nonce');
|
||||
|
||||
$manager = $this->createValidationManager($user);
|
||||
$activationUrl = $this->buildActivationUrl('[email protected]', 'current-nonce', time() - 60);
|
||||
|
||||
self::assertSame($user, $manager->validateActivationUrlForResend($activationUrl));
|
||||
}
|
||||
|
||||
public function testResendWelcomeEmailRotatesNonceAndSendsActivationMail(): void
|
||||
{
|
||||
$user = new User('[email protected]');
|
||||
$user->setWelcomeActivationNonce('old-nonce');
|
||||
|
||||
$generatedRoute = null;
|
||||
$generatedParameters = null;
|
||||
$generatedReferenceType = null;
|
||||
$capturedContext = null;
|
||||
$capturedOptions = null;
|
||||
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
|
||||
$entityManager->expects(self::once())->method('flush');
|
||||
$passwordHasher->expects(self::never())->method('hashPassword');
|
||||
$userRepository->expects(self::never())->method('findOneByNormalizedEmail');
|
||||
$urlGenerator
|
||||
->expects(self::once())
|
||||
->method('generate')
|
||||
->willReturnCallback(static function (string $route, array $parameters, int $referenceType) use (&$generatedRoute, &$generatedParameters, &$generatedReferenceType): string {
|
||||
$generatedRoute = $route;
|
||||
$generatedParameters = $parameters;
|
||||
$generatedReferenceType = $referenceType;
|
||||
|
||||
return 'https://example.test/welcome/activate?'.http_build_query($parameters);
|
||||
});
|
||||
$mailer
|
||||
->expects(self::once())
|
||||
->method('createAndSendEmail')
|
||||
->willReturnCallback(static function (array $context, array $options) use (&$capturedContext, &$capturedOptions): void {
|
||||
$capturedContext = $context;
|
||||
$capturedOptions = $options;
|
||||
});
|
||||
|
||||
$manager = $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer);
|
||||
|
||||
$manager->resendWelcomeEmail($user);
|
||||
|
||||
self::assertSame('app_guest_account_activate', $generatedRoute);
|
||||
self::assertSame(UrlGeneratorInterface::ABSOLUTE_URL, $generatedReferenceType);
|
||||
self::assertSame('[email protected]', $generatedParameters['email']);
|
||||
self::assertSame($user->getWelcomeActivationNonce(), $generatedParameters['nonce']);
|
||||
self::assertSame('[email protected]', $capturedOptions['to']);
|
||||
self::assertSame('Dein MyE&P Konto ist bereit', $capturedOptions['subject']);
|
||||
self::assertSame('email/booking_guest_account_welcome.html.twig', $capturedOptions['template']);
|
||||
self::assertTrue((new UriSigner('test-secret'))->check((string) $capturedContext['activationUrl']));
|
||||
self::assertNotSame('old-nonce', $user->getWelcomeActivationNonce());
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{64}$/', (string) $user->getWelcomeActivationNonce());
|
||||
}
|
||||
|
||||
public function testProvisionGuestAccountKeepsPersistedUserWhenMailSendFails(): void
|
||||
{
|
||||
$persistedUser = null;
|
||||
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$userRepository
|
||||
->expects(self::once())
|
||||
->method('findOneByNormalizedEmail')
|
||||
->with('[email protected]')
|
||||
->willReturn(null);
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager
|
||||
->expects(self::once())
|
||||
->method('persist')
|
||||
->with(self::callback(static function (User $user) use (&$persistedUser): bool {
|
||||
$persistedUser = $user;
|
||||
|
||||
return true;
|
||||
}));
|
||||
$entityManager->expects(self::once())->method('flush');
|
||||
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$passwordHasher
|
||||
->expects(self::once())
|
||||
->method('hashPassword')
|
||||
->willReturn('hashed-password');
|
||||
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$urlGenerator
|
||||
->expects(self::once())
|
||||
->method('generate')
|
||||
->willReturn('https://example.test/welcome/[email protected]&nonce=current&expires=9999999999');
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer
|
||||
->expects(self::once())
|
||||
->method('createAndSendEmail')
|
||||
->willThrowException(new TransportException('smtp down'));
|
||||
|
||||
$manager = $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer);
|
||||
|
||||
$this->expectException(TransportException::class);
|
||||
try {
|
||||
$manager->provisionGuestAccount('[email protected]', 'Alice', 'Example');
|
||||
} finally {
|
||||
self::assertInstanceOf(User::class, $persistedUser);
|
||||
self::assertSame('[email protected]', $persistedUser->getEmail());
|
||||
self::assertSame(['ROLE_USER', 'ROLE_CUSTOMER'], $persistedUser->getRoles());
|
||||
self::assertTrue($persistedUser->isProfileComplete());
|
||||
self::assertNotNull($persistedUser->getWelcomeActivationNonce());
|
||||
}
|
||||
}
|
||||
|
||||
private function createManager(
|
||||
UserRepository $userRepository,
|
||||
EntityManagerInterface $entityManager,
|
||||
UserPasswordHasherInterface $passwordHasher,
|
||||
UrlGeneratorInterface $urlGenerator,
|
||||
Mailer $mailer,
|
||||
?BpnConnectClient $bpnConnectClient = null,
|
||||
): BookingGuestAccountManager {
|
||||
$bpnConnectClient ??= $this->createMock(BpnConnectClient::class);
|
||||
|
||||
return new BookingGuestAccountManager(
|
||||
$bpnConnectClient,
|
||||
$userRepository,
|
||||
$entityManager,
|
||||
new UserBootstrapper(),
|
||||
$passwordHasher,
|
||||
$urlGenerator,
|
||||
new UriSigner('test-secret'),
|
||||
$mailer,
|
||||
new NullLogger(),
|
||||
);
|
||||
}
|
||||
|
||||
private function createValidationManager(User $user): BookingGuestAccountManager
|
||||
{
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$userRepository
|
||||
->method('findOneByNormalizedEmail')
|
||||
->willReturnCallback(static function (string $email) use ($user): ?User {
|
||||
return '[email protected]' === $email ? $user : null;
|
||||
});
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$bpnConnectClient = $this->createMock(BpnConnectClient::class);
|
||||
|
||||
return $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer, $bpnConnectClient);
|
||||
}
|
||||
|
||||
private function buildActivationUrl(string $email, string $nonce, int $expires): string
|
||||
{
|
||||
$baseUrl = 'https://example.test/welcome/activate?'.http_build_query([
|
||||
'email' => $email,
|
||||
'nonce' => $nonce,
|
||||
'expires' => $expires,
|
||||
]);
|
||||
|
||||
return (new UriSigner('test-secret'))->sign($baseUrl);
|
||||
}
|
||||
|
||||
private function createLookupResult(string $email, int $bookingNumber, string $firstName, string $lastName): BookingLookupResult
|
||||
{
|
||||
return new BookingLookupResult(
|
||||
bookingNumber: $bookingNumber,
|
||||
bookingId: 98765,
|
||||
addressId: 12345,
|
||||
personId: 67890,
|
||||
email: $email,
|
||||
firstName: $firstName,
|
||||
lastName: $lastName,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -173,6 +173,35 @@ class PasswordResetManagerTest extends TestCase
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{64}$/', (string) $user->getPasswordResetNonce());
|
||||
}
|
||||
|
||||
public function testResetPasswordClearsPendingWelcomeInvite(): void
|
||||
{
|
||||
$user = new User('[email protected]');
|
||||
$user->setPasswordResetNonce('current-nonce');
|
||||
$user->markWelcomeActivationPending('welcome-nonce', new \DateTimeImmutable('-30 minutes'));
|
||||
|
||||
$userRepository = $this->createMock(UserRepository::class);
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$passwordHasher = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
|
||||
$passwordHasher
|
||||
->expects(self::once())
|
||||
->method('hashPassword')
|
||||
->with($user, 'new-password')
|
||||
->willReturn('hashed-password');
|
||||
$entityManager->expects(self::once())->method('flush');
|
||||
|
||||
$manager = $this->createManager($userRepository, $entityManager, $passwordHasher, $urlGenerator, $mailer);
|
||||
|
||||
$manager->resetPassword($user, 'new-password');
|
||||
|
||||
self::assertSame('hashed-password', $user->getPassword());
|
||||
self::assertNull($user->getWelcomeActivationNonce());
|
||||
self::assertNull($user->getWelcomeActivationExpiresAt());
|
||||
self::assertNotNull($user->getWelcomeActivatedAt());
|
||||
}
|
||||
|
||||
public function testNewPasswordResetRequestInvalidatesPreviousLink(): void
|
||||
{
|
||||
$user = new User('[email protected]');
|
||||
|
||||
Reference in new issue
Block a user