750 lines
26 KiB
PHP
750 lines
26 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\BusProNet;
|
|
|
|
use App\BusProNet\Model\Address as BusProAddress;
|
|
use App\BusProNet\Model\Communication as BusProCommunication;
|
|
use App\BusProNet\Model\CrmAttributesResponse;
|
|
use App\BusProNet\Model\ProfileResponse;
|
|
use App\BusProNet\UserDataHandler;
|
|
use App\Entity\Embeddable\Address;
|
|
use App\Entity\Embeddable\Communication;
|
|
use App\Entity\Teamer;
|
|
use App\Entity\User;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Doctrine\ORM\EntityRepository;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
class UserDataHandlerTest extends TestCase
|
|
{
|
|
private EntityManagerInterface&MockObject $entityManager;
|
|
private LoggerInterface&MockObject $logger;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->entityManager = $this->createMock(EntityManagerInterface::class);
|
|
$this->logger = $this->createMock(LoggerInterface::class);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider collectRolesProvider
|
|
*/
|
|
public function testCollectRolesImportsAdministrativeRolesAsPendingOnly(CrmAttributesResponse $crmAttributes, array $expectedRoles): void
|
|
{
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
|
|
$this->assertSame($expectedRoles, $handler->collectRoles($crmAttributes));
|
|
}
|
|
|
|
public static function collectRolesProvider(): iterable
|
|
{
|
|
yield 'admin only yields the pending marker' => [
|
|
(new CrmAttributesResponse())->setAdmin(true),
|
|
[User::PENDING_ROLES['ROLE_ADMIN']],
|
|
];
|
|
|
|
yield 'manager only yields the pending marker' => [
|
|
(new CrmAttributesResponse())->setManager(true),
|
|
[User::PENDING_ROLES['ROLE_MANAGER']],
|
|
];
|
|
|
|
yield 'house manager only yields the pending marker' => [
|
|
(new CrmAttributesResponse())->setHouseManager(true),
|
|
[User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
|
|
];
|
|
|
|
yield 'teamer is granted directly' => [
|
|
(new CrmAttributesResponse())->setTeamer(true),
|
|
['ROLE_TEAMER'],
|
|
];
|
|
|
|
yield 'admin and teamer' => [
|
|
(new CrmAttributesResponse())->setAdmin(true)->setTeamer(true),
|
|
[User::PENDING_ROLES['ROLE_ADMIN'], 'ROLE_TEAMER'],
|
|
];
|
|
|
|
yield 'admin and manager yield both markers' => [
|
|
(new CrmAttributesResponse())->setAdmin(true)->setManager(true),
|
|
[User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_MANAGER']],
|
|
];
|
|
|
|
yield 'manager and house manager yield both markers, the roles stand on their own' => [
|
|
(new CrmAttributesResponse())->setManager(true)->setHouseManager(true),
|
|
[User::PENDING_ROLES['ROLE_MANAGER'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
|
|
];
|
|
|
|
yield 'house manager and teamer' => [
|
|
(new CrmAttributesResponse())->setHouseManager(true)->setTeamer(true),
|
|
[User::PENDING_ROLES['ROLE_HOUSE_MANAGER'], 'ROLE_TEAMER'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* The role policy has to be identical whichever identity source claims the role, so
|
|
* toPendingRoles() must agree with collectPendingRoles() on equivalent input.
|
|
*
|
|
* @dataProvider toPendingRolesProvider
|
|
*/
|
|
public function testToPendingRolesMatchesTheCrmPolicy(array $claimedRoles, CrmAttributesResponse $crmAttributes, array $expectedRoles): void
|
|
{
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
|
|
$this->assertSame($expectedRoles, $handler->toPendingRoles($claimedRoles));
|
|
$this->assertSame($expectedRoles, $handler->collectPendingRoles($crmAttributes));
|
|
}
|
|
|
|
public static function toPendingRolesProvider(): iterable
|
|
{
|
|
yield 'admin' => [
|
|
['ROLE_ADMIN'],
|
|
(new CrmAttributesResponse())->setAdmin(true),
|
|
[User::PENDING_ROLES['ROLE_ADMIN']],
|
|
];
|
|
|
|
yield 'manager and house manager are marked independently' => [
|
|
['ROLE_HOUSE_MANAGER', 'ROLE_MANAGER'],
|
|
(new CrmAttributesResponse())->setManager(true)->setHouseManager(true),
|
|
[User::PENDING_ROLES['ROLE_MANAGER'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
|
|
];
|
|
|
|
yield 'house manager' => [
|
|
['ROLE_HOUSE_MANAGER'],
|
|
(new CrmAttributesResponse())->setHouseManager(true),
|
|
[User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
|
|
];
|
|
|
|
yield 'admin and house manager' => [
|
|
['ROLE_ADMIN', 'ROLE_HOUSE_MANAGER'],
|
|
(new CrmAttributesResponse())->setAdmin(true)->setHouseManager(true),
|
|
[User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
|
|
];
|
|
|
|
yield 'teamer has no marker' => [
|
|
['ROLE_TEAMER'],
|
|
(new CrmAttributesResponse())->setTeamer(true),
|
|
[],
|
|
];
|
|
|
|
yield 'nothing claimed' => [
|
|
[],
|
|
new CrmAttributesResponse(),
|
|
[],
|
|
];
|
|
}
|
|
|
|
public function testUpdateLocalUserSyncsUserAndTeamerDataFromBusPro(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('Old')
|
|
->setLastName('Name')
|
|
->setEmail('[email protected]')
|
|
->setBusProAddressId(1)
|
|
->setBusProPersonId(2)
|
|
->setRoles(['ROLE_ADMIN'])
|
|
->setHotelCodes(['XYZ'])
|
|
;
|
|
|
|
$teamer = (new Teamer())
|
|
->setFirstName('OldTeamer')
|
|
->setLastName('OldLastname')
|
|
->setAcademicTitle('Dr.')
|
|
->setSalutation('Herr')
|
|
->setGender('M')
|
|
->setDateOfBirth(new \DateTimeImmutable('1990-01-01'))
|
|
->setAddress((new Address())->setStreet('Old Street')->setPostCode('11111')->setCity('Old City')->setCountry('DE'))
|
|
->setCommunication((new Communication())->setPhone('123')->setMobile('456')->setEmail('[email protected]'))
|
|
;
|
|
$user->setTeamer($teamer);
|
|
|
|
$profileResponse = $this->createProfileResponse();
|
|
|
|
$this->entityManager
|
|
->expects($this->once())
|
|
->method('flush');
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->updateLocalUser(
|
|
$user,
|
|
$profileResponse,
|
|
true,
|
|
['team' => ['selected' => true]],
|
|
['ROLE_ADMIN', 'ROLE_TEAMER'],
|
|
['DKS'],
|
|
);
|
|
|
|
$this->assertSame('New', $user->getFirstName());
|
|
$this->assertSame('Lastname', $user->getLastName());
|
|
$this->assertSame('[email protected]', $user->getEmail());
|
|
|
|
// the CRM leads: the still claimed role survives, the houses are replaced by its own
|
|
$this->assertSame(['DKS'], $user->getHotelCodes());
|
|
$this->assertTrue($user->hasRole('ROLE_ADMIN'));
|
|
|
|
$this->assertSame('New', $teamer->getFirstName());
|
|
$this->assertSame('Lastname', $teamer->getLastName());
|
|
$this->assertSame('Prof.', $teamer->getAcademicTitle());
|
|
$this->assertSame('Frau', $teamer->getSalutation());
|
|
$this->assertSame('F', $teamer->getGender());
|
|
$this->assertEquals(new \DateTimeImmutable('1995-12-24'), $teamer->getDateOfBirth());
|
|
$this->assertSame('New Street 123', $teamer->getAddress()?->getStreet());
|
|
$this->assertSame('54321', $teamer->getAddress()?->getPostCode());
|
|
$this->assertSame('New City', $teamer->getAddress()?->getCity());
|
|
$this->assertSame('AT', $teamer->getAddress()?->getCountry());
|
|
$this->assertSame('999', $teamer->getCommunication()?->getPhone());
|
|
$this->assertSame('888', $teamer->getCommunication()?->getMobile());
|
|
$this->assertSame('[email protected]', $teamer->getCommunication()?->getEmail());
|
|
$this->assertSame(['team' => ['selected' => true]], $teamer->getCrmSelections());
|
|
}
|
|
|
|
/**
|
|
* @dataProvider pendingRolesProvider
|
|
*/
|
|
public function testUpdateLocalUserRefreshesThePendingRoles(
|
|
array $roles,
|
|
array $claimedRoles,
|
|
array $expectedAssignedRoles,
|
|
array $expectedPendingRoles,
|
|
): void {
|
|
$user = (new User())
|
|
->setFirstName('Old')
|
|
->setLastName('Name')
|
|
->setEmail('[email protected]')
|
|
->setRoles($roles)
|
|
;
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->updateLocalUser($user, $this->createProfileResponse(), false, [], $claimedRoles);
|
|
|
|
$this->assertSame($expectedAssignedRoles, $user->getAssignedRoles());
|
|
$this->assertSame($expectedPendingRoles, $user->getPendingRoles());
|
|
}
|
|
|
|
public static function pendingRolesProvider(): iterable
|
|
{
|
|
yield 'marker is added when the CRM claims a manager' => [
|
|
['ROLE_TEAMER'],
|
|
['ROLE_MANAGER', 'ROLE_TEAMER'],
|
|
['ROLE_TEAMER'],
|
|
[User::PENDING_ROLES['ROLE_MANAGER']],
|
|
];
|
|
|
|
yield 'marker is dropped when the CRM attribute is gone' => [
|
|
[User::PENDING_ROLES['ROLE_HOUSE_MANAGER'], 'ROLE_TEAMER'],
|
|
['ROLE_TEAMER'],
|
|
['ROLE_TEAMER'],
|
|
[],
|
|
];
|
|
|
|
yield 'an approved role is never marked again' => [
|
|
['ROLE_MANAGER'],
|
|
['ROLE_MANAGER'],
|
|
['ROLE_MANAGER'],
|
|
[],
|
|
];
|
|
|
|
yield 'a claim beyond the approved role stays pending' => [
|
|
['ROLE_MANAGER'],
|
|
['ROLE_ADMIN', 'ROLE_MANAGER'],
|
|
['ROLE_MANAGER'],
|
|
[User::PENDING_ROLES['ROLE_ADMIN']],
|
|
];
|
|
|
|
yield 'the claimed role changes' => [
|
|
[User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
|
|
['ROLE_MANAGER'],
|
|
[],
|
|
[User::PENDING_ROLES['ROLE_MANAGER']],
|
|
];
|
|
|
|
yield 'a granted role is revoked once the CRM stops claiming it' => [
|
|
['ROLE_ADMIN', 'ROLE_TEAMER'],
|
|
['ROLE_TEAMER'],
|
|
['ROLE_TEAMER'],
|
|
[],
|
|
];
|
|
|
|
yield 'a manager who is also a house manager is marked for both' => [
|
|
[],
|
|
['ROLE_MANAGER', 'ROLE_HOUSE_MANAGER'],
|
|
[],
|
|
[User::PENDING_ROLES['ROLE_MANAGER'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
|
|
];
|
|
}
|
|
|
|
public function testUpdateLocalUserGrantsTheTeamerRoleToAUserWhoBecameATeamer(): void
|
|
{
|
|
// created as a candidate for approval, made a teamer in the CRM afterwards
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles([User::PENDING_ROLES['ROLE_ADMIN']])
|
|
;
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->updateLocalUser($user, $this->createProfileResponse(), true, [], ['ROLE_ADMIN', 'ROLE_TEAMER']);
|
|
|
|
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
|
|
$this->assertSame([User::PENDING_ROLES['ROLE_ADMIN']], $user->getPendingRoles());
|
|
}
|
|
|
|
/**
|
|
* ROLE_TEAMER needs no approval, but it is not exempt from the sync either: the CRM
|
|
* leads, so the role goes when the attribute does.
|
|
*/
|
|
public function testUpdateLocalUserWithdrawsTheTeamerRoleOfSomebodyTheCrmNoLongerReportsAsTeamer(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles(['ROLE_ADMIN', 'ROLE_TEAMER'])
|
|
;
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->updateLocalUser($user, $this->createProfileResponse(), false, [], ['ROLE_ADMIN']);
|
|
|
|
$this->assertSame(['ROLE_ADMIN'], $user->getAssignedRoles());
|
|
}
|
|
|
|
public function testUpdateLocalUserGrantsTheTeamerRoleOnlyOnce(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles(['ROLE_TEAMER'])
|
|
;
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->updateLocalUser($user, $this->createProfileResponse(), true, [], ['ROLE_TEAMER']);
|
|
|
|
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
|
|
}
|
|
|
|
/**
|
|
* ROLE_SUPER_ADMIN is not a stored role but a flag getRoles() turns into one, so
|
|
* revoking ROLE_ADMIN has to take it down explicitly - otherwise the highest privilege
|
|
* in the application would outlive the role it depends on.
|
|
*/
|
|
public function testUpdateLocalUserTakesTheSuperAdminFlagDownWithRoleAdmin(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles(['ROLE_ADMIN', 'ROLE_TEAMER'])
|
|
->setSuperAdmin(true)
|
|
;
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->updateLocalUser($user, $this->createProfileResponse(), true, [], ['ROLE_TEAMER']);
|
|
|
|
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
|
|
$this->assertFalse($user->isSuperAdmin());
|
|
$this->assertFalse($user->hasRole('ROLE_SUPER_ADMIN'));
|
|
}
|
|
|
|
public function testUpdateLocalUserKeepsTheSuperAdminFlagOfAStillClaimedAdmin(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles(['ROLE_ADMIN', 'ROLE_TEAMER'])
|
|
->setSuperAdmin(true)
|
|
;
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->updateLocalUser($user, $this->createProfileResponse(), false, [], ['ROLE_ADMIN']);
|
|
|
|
$this->assertTrue($user->isSuperAdmin());
|
|
$this->assertTrue($user->hasRole('ROLE_SUPER_ADMIN'));
|
|
}
|
|
|
|
public function testUpdateLocalUserReplacesTheHotelCodesWithTheOnesTheCrmReports(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles(['ROLE_HOUSE_MANAGER'])
|
|
->setHotelCodes(['SSL'])
|
|
;
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->updateLocalUser($user, $this->createProfileResponse(), false, [], ['ROLE_HOUSE_MANAGER'], ['DKS']);
|
|
|
|
$this->assertSame(['DKS'], $user->getHotelCodes());
|
|
}
|
|
|
|
/**
|
|
* Approval is the one place a role is granted at all, and it can only ever grant a role
|
|
* the CRM already claims - the marker is what says so.
|
|
*/
|
|
public function testApproveRoleGrantsTheRoleAndClearsItsMarker(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles([
|
|
'ROLE_TEAMER',
|
|
User::PENDING_ROLES['ROLE_ADMIN'],
|
|
User::PENDING_ROLES['ROLE_HOUSE_MANAGER'],
|
|
])
|
|
;
|
|
|
|
$this->entityManager->expects($this->once())->method('flush');
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
|
|
$this->assertTrue($handler->approveRole($user, 'ROLE_ADMIN'));
|
|
$this->assertSame(['ROLE_TEAMER', 'ROLE_ADMIN'], $user->getAssignedRoles());
|
|
|
|
// the other nomination is untouched: one decision at a time
|
|
$this->assertSame([User::PENDING_ROLES['ROLE_HOUSE_MANAGER']], $user->getPendingRoles());
|
|
}
|
|
|
|
/**
|
|
* @dataProvider unapprovableRoleProvider
|
|
*/
|
|
public function testApproveRoleRefusesARoleWithoutANomination(array $roles, string $role): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles($roles)
|
|
;
|
|
|
|
$this->entityManager->expects($this->never())->method('flush');
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
|
|
$this->assertFalse($handler->approveRole($user, $role));
|
|
$this->assertEqualsCanonicalizing(
|
|
$roles,
|
|
[...$user->getAssignedRoles(), ...$user->getPendingRoles()],
|
|
);
|
|
}
|
|
|
|
public static function unapprovableRoleProvider(): iterable
|
|
{
|
|
yield 'the CRM never claimed it' => [['ROLE_TEAMER'], 'ROLE_MANAGER'];
|
|
|
|
yield 'a different role is nominated' => [[User::PENDING_ROLES['ROLE_MANAGER']], 'ROLE_ADMIN'];
|
|
|
|
yield 'already granted, so there is no marker left' => [['ROLE_ADMIN'], 'ROLE_ADMIN'];
|
|
|
|
yield 'teamer has no nomination to approve' => [['ROLE_TEAMER'], 'ROLE_TEAMER'];
|
|
|
|
yield 'not a role at all' => [[User::PENDING_ROLES['ROLE_ADMIN']], 'ROLE_SUPER_ADMIN'];
|
|
}
|
|
|
|
public function testDisableForRevokedCrmRolesBlocksTheUserAndDropsThePendingMarkers(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles(['ROLE_ADMIN', User::PENDING_ROLES['ROLE_MANAGER']])
|
|
;
|
|
|
|
$this->entityManager
|
|
->expects($this->once())
|
|
->method('flush');
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->disableForRevokedCrmRoles($user);
|
|
|
|
$this->assertTrue($user->isDisabled());
|
|
$this->assertNotNull($user->getDisabledAt());
|
|
$this->assertSame('Für deinen Account liegt in BusPro keine Berechtigung mehr vor.', $user->getDisabledReason());
|
|
$this->assertSame('Automatisch gesperrt: keine Rollen in BusPro.', $user->getDisabledReasonInternal());
|
|
|
|
// the granted role is kept so the user stays reviewable, the marker is not
|
|
$this->assertSame(['ROLE_ADMIN'], $user->getAssignedRoles());
|
|
$this->assertSame([], $user->getPendingRoles());
|
|
}
|
|
|
|
public function testDisableForRevokedCrmRolesLeavesAnExistingBlockUntouchedButStillFlushes(): void
|
|
{
|
|
$disabledAt = new \DateTimeImmutable('2026-01-01 08:00:00');
|
|
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles(['ROLE_ADMIN'])
|
|
->setDisabledAt($disabledAt)
|
|
->setDisabledReason('Wegen Fehlverhaltens gesperrt.')
|
|
->setDisabledReasonInternal('Siehe Vorgang 4711.')
|
|
;
|
|
|
|
// findLocalUser() may have refreshed the BusPro ids on the way here
|
|
$this->entityManager
|
|
->expects($this->once())
|
|
->method('flush');
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->disableForRevokedCrmRoles($user);
|
|
|
|
$this->assertSame($disabledAt, $user->getDisabledAt());
|
|
$this->assertSame('Wegen Fehlverhaltens gesperrt.', $user->getDisabledReason());
|
|
$this->assertSame('Siehe Vorgang 4711.', $user->getDisabledReasonInternal());
|
|
}
|
|
|
|
public function testFindLocalUserFallsBackToUniqueEmailAndRefreshesBusProIds(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setBusProAddressId(1)
|
|
->setBusProPersonId(2)
|
|
;
|
|
|
|
$profileResponse = $this->createProfileResponse();
|
|
|
|
$repository = $this->createMock(EntityRepository::class);
|
|
$repository
|
|
->expects($this->once())
|
|
->method('findOneBy')
|
|
->with([
|
|
'busProAddressId' => 200,
|
|
'busProPersonId' => 100,
|
|
])
|
|
->willReturn(null);
|
|
|
|
$repository
|
|
->expects($this->once())
|
|
->method('findBy')
|
|
->with([
|
|
'email' => '[email protected]',
|
|
])
|
|
->willReturn([$user]);
|
|
|
|
$this->entityManager
|
|
->expects($this->once())
|
|
->method('getRepository')
|
|
->with(User::class)
|
|
->willReturn($repository);
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$resolvedUser = $handler->findLocalUser($profileResponse);
|
|
|
|
$this->assertSame($user, $resolvedUser);
|
|
$this->assertSame(200, $user->getBusProAddressId());
|
|
$this->assertSame(100, $user->getBusProPersonId());
|
|
}
|
|
|
|
public function testFindLocalUserSkipsEmailFallbackWhenMultipleUsersExist(): void
|
|
{
|
|
$userA = (new User())
|
|
->setFirstName('A')
|
|
->setLastName('A')
|
|
->setEmail('[email protected]')
|
|
->setBusProAddressId(1)
|
|
->setBusProPersonId(2)
|
|
;
|
|
$userB = (new User())
|
|
->setFirstName('B')
|
|
->setLastName('B')
|
|
->setEmail('[email protected]')
|
|
->setBusProAddressId(3)
|
|
->setBusProPersonId(4)
|
|
;
|
|
|
|
$profileResponse = $this->createProfileResponse();
|
|
|
|
$repository = $this->createMock(EntityRepository::class);
|
|
$repository
|
|
->expects($this->once())
|
|
->method('findOneBy')
|
|
->willReturn(null);
|
|
$repository
|
|
->expects($this->once())
|
|
->method('findBy')
|
|
->willReturn([$userA, $userB]);
|
|
|
|
$this->entityManager
|
|
->expects($this->once())
|
|
->method('getRepository')
|
|
->with(User::class)
|
|
->willReturn($repository);
|
|
|
|
$this->logger
|
|
->expects($this->once())
|
|
->method('warning')
|
|
->with(
|
|
'Unable to match local user by email: multiple users found',
|
|
[
|
|
'email' => '[email protected]',
|
|
'count' => 2,
|
|
],
|
|
);
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$resolvedUser = $handler->findLocalUser($profileResponse);
|
|
|
|
$this->assertNull($resolvedUser);
|
|
}
|
|
|
|
public function testFindLocalUserReturnsUniqueEmailMatchWhenBusProIdsAreMissing(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setBusProAddressId(1)
|
|
->setBusProPersonId(2)
|
|
;
|
|
|
|
$profileResponse = $this->createProfileResponse(null, null);
|
|
|
|
$repository = $this->createMock(EntityRepository::class);
|
|
$repository
|
|
->expects($this->once())
|
|
->method('findOneBy')
|
|
->with([
|
|
'busProAddressId' => null,
|
|
'busProPersonId' => null,
|
|
])
|
|
->willReturn(null);
|
|
$repository
|
|
->expects($this->once())
|
|
->method('findBy')
|
|
->with([
|
|
'email' => '[email protected]',
|
|
])
|
|
->willReturn([$user]);
|
|
|
|
$this->entityManager
|
|
->expects($this->once())
|
|
->method('getRepository')
|
|
->with(User::class)
|
|
->willReturn($repository);
|
|
|
|
$this->logger
|
|
->expects($this->once())
|
|
->method('warning')
|
|
->with(
|
|
'Skip BusPro ID refresh: profile response missing IDs',
|
|
[
|
|
'user_id' => null,
|
|
'user_email' => '[email protected]',
|
|
'bus_pro_address_id' => null,
|
|
'bus_pro_person_id' => null,
|
|
],
|
|
);
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$resolvedUser = $handler->findLocalUser($profileResponse);
|
|
|
|
$this->assertSame($user, $resolvedUser);
|
|
$this->assertSame(1, $user->getBusProAddressId());
|
|
$this->assertSame(2, $user->getBusProPersonId());
|
|
}
|
|
|
|
/**
|
|
* A deleted account still has to be matched by email, otherwise the caller takes the
|
|
* person for unknown and creates a second account for them - which would resurrect
|
|
* them under a new row and defeat the deletion entirely. Nothing is written to it.
|
|
*/
|
|
public function testFindLocalUserReturnsADeletedEmailMatchWithoutWritingToIt(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setBusProAddressId(1)
|
|
->setBusProPersonId(2)
|
|
;
|
|
$user->setDeleted();
|
|
|
|
$profileResponse = $this->createProfileResponse();
|
|
|
|
$repository = $this->createMock(EntityRepository::class);
|
|
$repository
|
|
->expects($this->once())
|
|
->method('findOneBy')
|
|
->willReturn(null);
|
|
|
|
$repository
|
|
->expects($this->once())
|
|
->method('findBy')
|
|
->with([
|
|
'email' => '[email protected]',
|
|
])
|
|
->willReturn([$user]);
|
|
|
|
$this->entityManager
|
|
->expects($this->once())
|
|
->method('getRepository')
|
|
->with(User::class)
|
|
->willReturn($repository);
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$resolvedUser = $handler->findLocalUser($profileResponse);
|
|
|
|
$this->assertSame($user, $resolvedUser);
|
|
$this->assertSame(1, $user->getBusProAddressId());
|
|
$this->assertSame(2, $user->getBusProPersonId());
|
|
}
|
|
|
|
public function testDisableForRevokedCrmRolesLeavesADeletedAccountAlone(): void
|
|
{
|
|
$user = (new User())
|
|
->setFirstName('First')
|
|
->setLastName('Last')
|
|
->setEmail('[email protected]')
|
|
->setRoles(['ROLE_TEAMER'])
|
|
;
|
|
$user->setDeleted();
|
|
|
|
$this->entityManager
|
|
->expects($this->never())
|
|
->method('flush');
|
|
|
|
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
|
$handler->disableForRevokedCrmRoles($user);
|
|
|
|
$this->assertFalse($user->isDisabled());
|
|
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
|
|
}
|
|
|
|
private function createProfileResponse(?int $addressId = 200, ?int $personId = 100): ProfileResponse
|
|
{
|
|
$address = (new BusProAddress())
|
|
->setStreet('New Street 123')
|
|
->setPostCode('54321')
|
|
->setCity('New City')
|
|
->setCountry('AT')
|
|
;
|
|
|
|
$communication = (new BusProCommunication())
|
|
->setPhone('999')
|
|
->setMobile('888')
|
|
->setEmail('[email protected]')
|
|
;
|
|
|
|
return (new ProfileResponse())
|
|
->setAddressId($addressId)
|
|
->setPersonId($personId)
|
|
->setFirstName('New')
|
|
->setName('Lastname')
|
|
->setTitle('Prof.')
|
|
->setSalutation('Frau')
|
|
->setGender('F')
|
|
->setDateOfBirth(new \DateTimeImmutable('1995-12-24'))
|
|
->setAddress($address)
|
|
->setCommunication($communication)
|
|
;
|
|
}
|
|
}
|