$allowedOperations * @return void */ public static function setAllowedOperations(array $allowedOperations): void { static::$allowedOperations = $allowedOperations; } public static function getAllowedOperations(): array { return static::$allowedOperations; } public function supports(string $attribute, mixed $subject): bool { return parent::supports($attribute, $subject); } public function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { return parent::voteOnAttribute($attribute, $subject, $token); } public function canView(object $subject): bool { return true; } public function canEdit(object $subject): bool { return true; } public function canCreate(object $subject): bool { return true; } public function canDelete(object $subject): bool { return true; } public function isUserLoggedIn(): bool { return parent::isUserLoggedIn(); } public function getUser(): ?Access { return parent::getUser(); } public function isValidInternalRequest(): bool { return parent::isValidInternalRequest(); } } class AbstractVoterTest extends TestCase { protected Security | MockObject $security; protected Utils | MockObject $accessUtils; protected EntityManagerInterface | MockObject $entityManager; protected InternalRequestsService | MockObject $internalRequestsService; protected SwitchUser | MockObject $switchUserService; private bool $initialConditionSaved = false; private ?string $initialEntityClass = null; private ?array $initialAllowedOperations = null; private ?int $initialSwitchHeader = null; private ?string $initialRemoteAddr = null; private ?string $initialInternalRequestToken = null; private function saveInitialCondition() { $this->initialEntityClass = TestableAbstractVoter::getEntityClass(); $this->initialAllowedOperations = TestableAbstractVoter::getAllowedOperations(); $this->initialSwitchHeader = $_SERVER['HTTP_X_SWITCH_USER'] ?? null; $this->initialRemoteAddr = $_SERVER['REMOTE_ADDR'] ?? null; $this->initialInternalRequestToken = $_SERVER['HTTP_INTERNAL_REQUESTS_TOKEN'] ?? null; $this->initialConditionSaved = true; } private function reinitialize() { // Reinitialize the TestableAbstractVoter static properties TestableAbstractVoter::setEntityClass($this->initialEntityClass); TestableAbstractVoter::setAllowedOperations($this->initialAllowedOperations); // Reinitialize the global variables if ($this->initialSwitchHeader !== null) { $_SERVER['HTTP_X_SWITCH_USER'] = $this->initialSwitchHeader; } else if (array_key_exists('HTTP_X_SWITCH_USER', $_SERVER)) { unset($_SERVER['HTTP_X_SWITCH_USER']); } $_SERVER['REMOTE_ADDR'] = $this->initialRemoteAddr; if ($this->initialSwitchHeader !== null) { $_SERVER['HTTP_INTERNAL_REQUESTS_TOKEN'] = $this->initialSwitchHeader; } else if (array_key_exists('HTTP_INTERNAL_REQUESTS_TOKEN', $_SERVER)) { unset($_SERVER['HTTP_INTERNAL_REQUESTS_TOKEN']); } } public function setUp(): void { if (!$this->initialConditionSaved) { $this->saveInitialCondition(); } $this->security = $this->getMockBuilder(Security::class)->disableOriginalConstructor()->getMock(); $this->accessUtils = $this->getMockBuilder(Utils::class)->disableOriginalConstructor()->getMock(); $this->internalRequestsService = $this->getMockBuilder(InternalRequestsService::class)->disableOriginalConstructor()->getMock(); $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock(); $this->switchUserService = $this->getMockBuilder(SwitchUser::class)->disableOriginalConstructor()->getMock(); } public function tearDown(): void { $this->reinitialize(); } private function makeAbstractVoterMockFor(string $methodName): MockObject | TestableAbstractVoter { return $this->getMockBuilder(TestableAbstractVoter::class) ->setConstructorArgs([ $this->security, $this->accessUtils, $this->internalRequestsService, $this->entityManager, $this->switchUserService ]) ->setMethodsExcept([$methodName]) ->getMock(); } public function testSupports(): void { TestableAbstractVoter::setEntityClass(Access::class); $abstractVoter = $this->makeAbstractVoterMockFor('supports'); $access = new Access(); $this->assertTrue( $abstractVoter->supports(TestableAbstractVoter::READ, $access) ); $this->assertTrue( $abstractVoter->supports(TestableAbstractVoter::EDIT, $access) ); $this->assertTrue( $abstractVoter->supports(TestableAbstractVoter::CREATE, $access) ); $this->assertTrue( $abstractVoter->supports(TestableAbstractVoter::DELETE, $access) ); } public function testSupportsNotSupportedClass(): void { TestableAbstractVoter::setEntityClass(Access::class); $abstractVoter = $this->makeAbstractVoterMockFor('supports'); $person = new Person(); $this->assertFalse( $abstractVoter->supports(TestableAbstractVoter::READ, $person) ); } public function testSupportsNotAllowedOperation(): void { TestableAbstractVoter::setEntityClass(Access::class); TestableAbstractVoter::setAllowedOperations([TestableAbstractVoter::READ]); $abstractVoter = $this->makeAbstractVoterMockFor('supports'); $access = new Access(); $this->assertTrue( $abstractVoter->supports(TestableAbstractVoter::READ, $access) ); $this->assertFalse( $abstractVoter->supports(TestableAbstractVoter::EDIT, $access) ); $this->assertFalse( $abstractVoter->supports(TestableAbstractVoter::CREATE, $access) ); $this->assertFalse( $abstractVoter->supports(TestableAbstractVoter::DELETE, $access) ); } public function testSupportsMissingEntityClass(): void { $abstractVoter = $this->makeAbstractVoterMockFor('supports'); $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('Setup the self::$entityClass property, or override the supports() method'); $access = new Access(); $abstractVoter->supports(TestableAbstractVoter::READ, $access); } public function testVoteOnAttributeRead(): void { $abstractVoter = $this->makeAbstractVoterMockFor('voteOnAttribute'); $subject = $this->getMockBuilder(Access::class)->getMock(); $token = $this->getMockBuilder(TokenInterface::class)->getMock(); $abstractVoter->expects(self::once())->method('canView')->with($subject)->willReturn(true); $abstractVoter->expects(self::never())->method('canEdit'); $abstractVoter->expects(self::never())->method('canCreate'); $abstractVoter->expects(self::never())->method('canDelete'); $this->assertTrue( $abstractVoter->voteOnAttribute(TestableAbstractVoter::READ, $subject, $token) ); } public function testVoteOnAttributeEdit(): void { $abstractVoter = $this->makeAbstractVoterMockFor('voteOnAttribute'); $subject = $this->getMockBuilder(Access::class)->getMock(); $token = $this->getMockBuilder(TokenInterface::class)->getMock(); $abstractVoter->expects(self::never())->method('canView'); $abstractVoter->expects(self::once())->method('canEdit')->with($subject)->willReturn(true); $abstractVoter->expects(self::never())->method('canCreate'); $abstractVoter->expects(self::never())->method('canDelete'); $this->assertTrue( $abstractVoter->voteOnAttribute(TestableAbstractVoter::EDIT, $subject, $token) ); } public function testVoteOnAttributeCreate(): void { $abstractVoter = $this->makeAbstractVoterMockFor('voteOnAttribute'); $subject = $this->getMockBuilder(Access::class)->getMock(); $token = $this->getMockBuilder(TokenInterface::class)->getMock(); $abstractVoter->expects(self::never())->method('canView'); $abstractVoter->expects(self::never())->method('canEdit'); $abstractVoter->expects(self::once())->method('canCreate')->with($subject)->willReturn(true); $abstractVoter->expects(self::never())->method('canDelete'); $this->assertTrue( $abstractVoter->voteOnAttribute(TestableAbstractVoter::CREATE, $subject, $token) ); } public function testVoteOnAttributeDelete(): void { $abstractVoter = $this->makeAbstractVoterMockFor('voteOnAttribute'); $subject = $this->getMockBuilder(Access::class)->getMock(); $token = $this->getMockBuilder(TokenInterface::class)->getMock(); $abstractVoter->expects(self::never())->method('canView'); $abstractVoter->expects(self::never())->method('canEdit'); $abstractVoter->expects(self::never())->method('canCreate'); $abstractVoter->expects(self::once())->method('canDelete')->with($subject)->willReturn(true); $this->assertTrue( $abstractVoter->voteOnAttribute(TestableAbstractVoter::DELETE, $subject, $token) ); } public function testVoteOnAttributeInvalidAttribute(): void { $abstractVoter = $this->makeAbstractVoterMockFor('voteOnAttribute'); $subject = $this->getMockBuilder(Access::class)->getMock(); $token = $this->getMockBuilder(TokenInterface::class)->getMock(); $abstractVoter->expects(self::never())->method('canView'); $abstractVoter->expects(self::never())->method('canEdit'); $abstractVoter->expects(self::never())->method('canCreate'); $abstractVoter->expects(self::never())->method('canDelete'); $this->assertFalse( $abstractVoter->voteOnAttribute('other_attr', $subject, $token) ); } public function testGetUser() { $user = $this->getMockBuilder(Access::class)->getMock(); $this->security->expects(self::once())->method('getUser')->willReturn($user); $abstractVoter = $this->makeAbstractVoterMockFor('getUser'); $this->assertEquals( $user, $abstractVoter->getUser() ); } public function testGetUserNoUser() { $this->security->expects(self::once())->method('getUser')->willReturn(null); $abstractVoter = $this->makeAbstractVoterMockFor('getUser'); $this->assertNull( $abstractVoter->getUser() ); } public function testGetUserSwitchUser() { $originalUser = $this->getMockBuilder(Access::class)->getMock(); $switchUser = $this->getMockBuilder(Access::class)->getMock(); $_SERVER['HTTP_X_SWITCH_USER'] = 1; $this->security->expects(self::once())->method('getUser')->willReturn($originalUser); $this->entityManager->expects(self::once())->method('find')->with(Access::class, 1)->willReturn($switchUser); $abstractVoter = $this->makeAbstractVoterMockFor('getUser'); $this->assertEquals( $switchUser, $abstractVoter->getUser() ); } public function testIsUserLoggedIn(): void { $user = $this->getMockBuilder(Access::class)->getMock(); $abstractVoter = $this->makeAbstractVoterMockFor('isUserLoggedIn'); $abstractVoter->expects(self::once())->method('getUser')->willReturn($user); $this->assertTrue($abstractVoter->isUserLoggedIn()); } public function testIsUserLoggedInNoUser(): void { $abstractVoter = $this->makeAbstractVoterMockFor('isUserLoggedIn'); $abstractVoter->expects(self::once())->method('getUser')->willReturn(null); $this->assertFalse($abstractVoter->isUserLoggedIn()); } public function testIsValidInternalRequest(): void { $clientIp = '0.0.0.0'; $token = 'valid_token'; $_SERVER['REMOTE_ADDR'] = $clientIp; $_SERVER['HTTP_INTERNAL_REQUESTS_TOKEN'] = $token; $this->internalRequestsService ->expects(self::once()) ->method('isAllowed') ->with($clientIp, $token) ->willReturn(true); $abstractVoter = $this->makeAbstractVoterMockFor('isValidInternalRequest'); $this->assertTrue($abstractVoter->isValidInternalRequest()); } }