feat: streamlined role-revocation logic
This commit is contained in:
@@ -48,6 +48,59 @@ class ResponseParserTest extends TestCase
|
||||
$this->assertTrue($response->isTeamer());
|
||||
}
|
||||
|
||||
/**
|
||||
* BusPro always returns the full attribute tree and expresses the roles a person holds
|
||||
* through the "auswahl" flag, so a revoked role arrives as a selection set to False,
|
||||
* never as a missing group. The demotion path in BpnAuthenticator relies on that: it
|
||||
* treats "no roles" as a revocation only when attribute groups are present.
|
||||
*/
|
||||
public function testParseCrmAttributesOfAUserHoldingEveryRole(): void
|
||||
{
|
||||
$parser = $this->getParserInstance();
|
||||
$response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $this->loadFixture('crm_attributes_granted.xml'));
|
||||
|
||||
$this->assertInstanceOf(CrmAttributesResponse::class, $response);
|
||||
$this->assertTrue($response->isTeamer());
|
||||
$this->assertTrue($response->isAdmin());
|
||||
$this->assertTrue($response->isManager());
|
||||
$this->assertTrue($response->isHouseManager());
|
||||
$this->assertSame(['DKS'], $response->getHotelCodes());
|
||||
|
||||
// "Preisrechner Admin" is matched by id, never by its label
|
||||
$this->assertCount(3, $response->getAttributeGroups());
|
||||
}
|
||||
|
||||
public function testParseCrmAttributesOfAUserWhoseRolesWereRevoked(): void
|
||||
{
|
||||
$parser = $this->getParserInstance();
|
||||
$response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $this->loadFixture('crm_attributes_revoked.xml'));
|
||||
|
||||
$this->assertInstanceOf(CrmAttributesResponse::class, $response);
|
||||
$this->assertFalse($response->isTeamer());
|
||||
$this->assertFalse($response->isAdmin());
|
||||
$this->assertFalse($response->isManager());
|
||||
$this->assertFalse($response->isHouseManager());
|
||||
$this->assertSame([], $response->getHotelCodes());
|
||||
|
||||
// the groups still arrive, so this is a revocation and not a degraded response
|
||||
$this->assertCount(3, $response->getAttributeGroups());
|
||||
}
|
||||
|
||||
public function testParseCrmAttributesOfAnEmptyResponse(): void
|
||||
{
|
||||
$content = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="KUNDENKONTO"></satz><art>SelektionCRM</art><idadresse>141747</idadresse><idperson>224526</idperson><selektionsmerkmale></selektionsmerkmale></ergebnis>';
|
||||
|
||||
$parser = $this->getParserInstance();
|
||||
$response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $content);
|
||||
|
||||
// indistinguishable from a revocation by the roles alone, which is why the empty
|
||||
// group set is what the demotion path checks
|
||||
$this->assertInstanceOf(CrmAttributesResponse::class, $response);
|
||||
$this->assertFalse($response->isTeamer());
|
||||
$this->assertSame([], $response->getAttributeGroups());
|
||||
$this->assertSame([], $response->toArray());
|
||||
}
|
||||
|
||||
public function testParseSuccessfulProfileUpdateResponseWithoutAddressData(): void
|
||||
{
|
||||
$content = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="KUNDENKONTO"></satz><art>Adressdaten_Ändern</art><idadresse>141747</idadresse><idperson>224526</idperson><änderung>True</änderung></ergebnis>';
|
||||
@@ -106,6 +159,11 @@ class ResponseParserTest extends TestCase
|
||||
$this->assertEquals('Gaststätte', $hotel->getType());
|
||||
}
|
||||
|
||||
private function loadFixture(string $filename): string
|
||||
{
|
||||
return file_get_contents(__DIR__.'/../Resources/'.$filename);
|
||||
}
|
||||
|
||||
private function getParserInstance(): ResponseParser
|
||||
{
|
||||
return new ResponseParser([
|
||||
|
||||
@@ -213,6 +213,54 @@ class UserDataHandlerTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
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, [], [User::PENDING_ROLES['ROLE_ADMIN']]);
|
||||
|
||||
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
|
||||
$this->assertSame([User::PENDING_ROLES['ROLE_ADMIN']], $user->getPendingRoles());
|
||||
}
|
||||
|
||||
public function testUpdateLocalUserKeepsTheTeamerRoleOfSomebodyTheCrmNoLongerReportsAsTeamer(): void
|
||||
{
|
||||
// the role may have been granted manually and must survive a login
|
||||
$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, [], []);
|
||||
|
||||
$this->assertSame(['ROLE_ADMIN', 'ROLE_TEAMER'], $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, [], []);
|
||||
|
||||
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
|
||||
}
|
||||
|
||||
public function testDisableForRevokedCrmRolesBlocksTheUserAndDropsThePendingMarkers(): void
|
||||
{
|
||||
$user = (new User())
|
||||
@@ -239,7 +287,7 @@ class UserDataHandlerTest extends TestCase
|
||||
$this->assertSame([], $user->getPendingRoles());
|
||||
}
|
||||
|
||||
public function testDisableForRevokedCrmRolesLeavesAnExistingBlockUntouched(): void
|
||||
public function testDisableForRevokedCrmRolesLeavesAnExistingBlockUntouchedButStillFlushes(): void
|
||||
{
|
||||
$disabledAt = new \DateTimeImmutable('2026-01-01 08:00:00');
|
||||
|
||||
@@ -253,8 +301,9 @@ class UserDataHandlerTest extends TestCase
|
||||
->setDisabledReasonInternal('Siehe Vorgang 4711.')
|
||||
;
|
||||
|
||||
// findLocalUser() may have refreshed the BusPro ids on the way here
|
||||
$this->entityManager
|
||||
->expects($this->never())
|
||||
->expects($this->once())
|
||||
->method('flush');
|
||||
|
||||
$handler = new UserDataHandler($this->entityManager, $this->logger);
|
||||
|
||||
Reference in New Issue
Block a user