feat: bpn as single source of truth for role and hotel code assignments

This commit is contained in:
Björn Fromme
2026-08-18 11:33:42 +02:00
parent 1fd0fbc21e
commit f0e850978b
17 changed files with 818 additions and 198 deletions
+152 -20
View File
@@ -72,9 +72,9 @@ class UserDataHandlerTest extends TestCase
[User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_MANAGER']],
];
yield 'manager takes precedence over house 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_MANAGER'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
];
yield 'house manager and teamer' => [
@@ -105,10 +105,10 @@ class UserDataHandlerTest extends TestCase
[User::PENDING_ROLES['ROLE_ADMIN']],
];
yield 'manager takes precedence over house manager' => [
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_MANAGER'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
];
yield 'house manager' => [
@@ -172,14 +172,16 @@ class UserDataHandlerTest extends TestCase
$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());
// roles and hotel codes are imported on creation only and stay under manual control
$this->assertSame(['XYZ'], $user->getHotelCodes());
// 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());
@@ -225,44 +227,51 @@ class UserDataHandlerTest extends TestCase
{
yield 'marker is added when the CRM claims a manager' => [
['ROLE_TEAMER'],
[User::PENDING_ROLES['ROLE_MANAGER']],
['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'],
[User::PENDING_ROLES['ROLE_MANAGER']],
['ROLE_MANAGER'],
['ROLE_MANAGER'],
[],
];
yield 'a claim beyond the approved role stays pending' => [
['ROLE_MANAGER'],
[User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_MANAGER']],
['ROLE_ADMIN', 'ROLE_MANAGER'],
['ROLE_MANAGER'],
[User::PENDING_ROLES['ROLE_ADMIN']],
];
yield 'the claimed role changes' => [
[User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
[User::PENDING_ROLES['ROLE_MANAGER']],
['ROLE_MANAGER'],
[],
[User::PENDING_ROLES['ROLE_MANAGER']],
];
yield 'granted roles are untouched without any claim' => [
['ROLE_ADMIN'],
yield 'a granted role is revoked once the CRM stops claiming it' => [
['ROLE_ADMIN', 'ROLE_TEAMER'],
['ROLE_TEAMER'],
['ROLE_TEAMER'],
[],
['ROLE_ADMIN'],
];
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']],
];
}
@@ -277,15 +286,18 @@ class UserDataHandlerTest extends TestCase
;
$handler = new UserDataHandler($this->entityManager, $this->logger);
$handler->updateLocalUser($user, $this->createProfileResponse(), true, [], [User::PENDING_ROLES['ROLE_ADMIN']]);
$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());
}
public function testUpdateLocalUserKeepsTheTeamerRoleOfSomebodyTheCrmNoLongerReportsAsTeamer(): void
/**
* 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
{
// the role may have been granted manually and must survive a login
$user = (new User())
->setFirstName('First')
->setLastName('Last')
@@ -294,9 +306,9 @@ class UserDataHandlerTest extends TestCase
;
$handler = new UserDataHandler($this->entityManager, $this->logger);
$handler->updateLocalUser($user, $this->createProfileResponse(), false, [], []);
$handler->updateLocalUser($user, $this->createProfileResponse(), false, [], ['ROLE_ADMIN']);
$this->assertSame(['ROLE_ADMIN', 'ROLE_TEAMER'], $user->getAssignedRoles());
$this->assertSame(['ROLE_ADMIN'], $user->getAssignedRoles());
}
public function testUpdateLocalUserGrantsTheTeamerRoleOnlyOnce(): void
@@ -309,11 +321,131 @@ class UserDataHandlerTest extends TestCase
;
$handler = new UserDataHandler($this->entityManager, $this->logger);
$handler->updateLocalUser($user, $this->createProfileResponse(), true, [], []);
$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())