'KO-Experte'], Role::nominatedFrom($roles), ); } public function testApprovedCustomerExpertDisplacesTheCustomerFallback(): void { $roles = Role::approve([Role::pending(Role::CUSTOMER_EXPERT), Role::CUSTOMER], Role::CUSTOMER_EXPERT); self::assertSame([Role::CUSTOMER_EXPERT], $roles); } public function testApprovedRoleSurvivesTheNextSyncAndIsNotMarkedAgain(): void { $roles = Role::sync([Role::TEAMER, Role::GROUPS_ADMIN], [Role::GROUPS_ADMIN, Role::TEAMER]); self::assertSame([Role::TEAMER, Role::GROUPS_ADMIN], $roles); } public function testRoleTheCrmNoLongerClaimsIsRevoked(): void { // Both halves go: BusPro is the source of truth for the granted role as much as for // the nomination. $roles = Role::sync([Role::TEAMER, Role::ADMIN, Role::pending(Role::MANAGER)], [Role::TEAMER]); self::assertSame([Role::TEAMER], $roles); } public function testRevokedRoleIsNotImmediatelyNominatedAgain(): void { self::assertSame([Role::CUSTOMER], Role::sync([Role::ADMIN], [])); } public function testAccountWithoutAnEffectiveRoleFallsBackToCustomer(): void { // The nomination stays visible — it is what an approver acts on — but grants nothing, // so the account is a customer in the meantime. self::assertSame( [Role::pending(Role::ADMIN), Role::CUSTOMER], Role::sync([], [Role::ADMIN]), ); self::assertSame([Role::CUSTOMER], Role::sync([], [])); } public function testCustomerIsAFallbackAndNotABaseline(): void { self::assertSame([Role::TEAMER], Role::sync([Role::CUSTOMER], [Role::TEAMER])); } public function testUnknownClaimsAndTheImplicitRoleUserAreIgnored(): void { self::assertSame( [Role::TEAMER], Role::sync([Role::USER, Role::TEAMER], [Role::TEAMER, 'ROLE_SOMETHING_ELSE']), ); } public function testApprovalTurnsTheNominationIntoTheRole(): void { $roles = Role::approve([Role::pending(Role::ADMIN), Role::CUSTOMER], Role::ADMIN); // The customer fallback goes with it: the account now holds an effective role. self::assertSame([Role::ADMIN], $roles); } public function testApprovingARoleWithoutANominationIsRefused(): void { $this->expectException(\InvalidArgumentException::class); Role::approve([Role::TEAMER], Role::ADMIN); } public function testEffectiveRolesExcludeNominationsAndTheImplicitRoleUser(): void { $roles = [Role::USER, Role::TEAMER, Role::pending(Role::ADMIN)]; self::assertSame([Role::TEAMER], Role::effectiveOnly($roles)); self::assertSame([Role::pending(Role::ADMIN)], Role::pendingOnly($roles)); self::assertSame([Role::ADMIN => 'Administration'], Role::nominatedFrom($roles)); } public function testEmployeeIsGrantedOutrightAndDisplacesTheCustomerFallback(): void { // The claim does not come from the CRM, but it travels the same path as one. self::assertSame([Role::EMPLOYEE], Role::sync([], [Role::EMPLOYEE])); } public function testEmployeeIsRevokedOnceItIsNoLongerClaimed(): void { // Somebody whose address left the staff domain: no claim, so the role goes, and with no // effective role left the fallback returns. self::assertSame([Role::CUSTOMER], Role::sync([Role::EMPLOYEE], [])); } public function testEmployeeDoesNotShortCircuitTheNominationOfAnAdministrativeRole(): void { self::assertSame( [Role::EMPLOYEE, Role::pending(Role::ADMIN)], Role::sync([], [Role::EMPLOYEE, Role::ADMIN]), ); } public function testEmployeeAndTeamerCoexist(): void { self::assertSame( [Role::TEAMER, Role::EMPLOYEE], Role::sync([], [Role::TEAMER, Role::EMPLOYEE]), ); } public function testEveryRoleAndNominationHasALabel(): void { $labels = Role::labels(); foreach (Role::ALL as $role) { self::assertArrayHasKey($role, $labels); } foreach (Role::ADMINISTRATIVE as $role) { self::assertArrayHasKey(Role::pending($role), $labels); } } }