clientRegistry = $this->createMock(ClientRegistry::class); $this->em = $this->createMock(EntityManagerInterface::class); $this->router = $this->createMock(RouterInterface::class); $this->authenticator = new KeycloakAuthenticator( $this->clientRegistry, $this->em, $this->router, ); } public function testSupportsReturnsTrueForCheckRoute(): void { $request = new Request(); $request->attributes->set('_route', 'app_oauth_keycloak_check'); self::assertTrue($this->authenticator->supports($request)); } public function testSupportsReturnsFalseForOtherRoute(): void { $request = new Request(); $request->attributes->set('_route', 'app_login'); self::assertFalse($this->authenticator->supports($request)); } public function testOnAuthenticationSuccessRedirectsToAccount(): void { $this->router->method('generate') ->with('app_account') ->willReturn('/mon-compte'); $request = new Request(); $token = $this->createMock(TokenInterface::class); $response = $this->authenticator->onAuthenticationSuccess($request, $token, 'main'); self::assertSame(302, $response->getStatusCode()); self::assertSame('/mon-compte', $response->getTargetUrl()); } public function testOnAuthenticationFailureRedirectsToLoginWithFlash(): void { $this->router->method('generate') ->with('app_login') ->willReturn('/connexion'); $flashBag = new FlashBag(); $session = $this->createMock(Session::class); $session->method('getFlashBag')->willReturn($flashBag); $request = new Request(); $request->setSession($session); $exception = new AuthenticationException('Test error'); $response = $this->authenticator->onAuthenticationFailure($request, $exception); self::assertSame(302, $response->getStatusCode()); self::assertSame('/connexion', $response->getTargetUrl()); self::assertSame(['Echec de la connexion SSO E-Cosplay.'], $flashBag->get('error')); } public function testAuthenticateCreatesNewUser(): void { $keycloakUser = $this->createKeycloakUser('kc-123', 'new@example.com', 'Jean', 'Dupont', []); $repo = $this->createMock(EntityRepository::class); $repo->method('findOneBy')->willReturn(null); $this->em->method('getRepository')->willReturn($repo); $this->em->expects(self::once())->method('persist'); $this->em->expects(self::once())->method('flush'); $passport = $this->callAuthenticate($keycloakUser); $user = $passport->getUser(); self::assertInstanceOf(User::class, $user); self::assertSame('new@example.com', $user->getEmail()); self::assertSame('Jean', $user->getFirstName()); self::assertSame('Dupont', $user->getLastName()); self::assertSame('kc-123', $user->getKeycloakId()); } public function testAuthenticateLinksExistingUserByEmail(): void { $existingUser = new User(); $existingUser->setEmail('existing@example.com'); $existingUser->setFirstName('Existing'); $existingUser->setLastName('User'); $existingUser->setPassword('$2y$13$hashed'); $keycloakUser = $this->createKeycloakUser('kc-456', 'existing@example.com', 'Existing', 'User', []); $repo = $this->createMock(EntityRepository::class); $repo->method('findOneBy')->willReturnCallback(function (array $criteria) use ($existingUser) { if (isset($criteria['keycloakId'])) { return null; } return $existingUser; }); $this->em->method('getRepository')->willReturn($repo); $this->em->expects(self::never())->method('persist'); $this->em->expects(self::once())->method('flush'); $passport = $this->callAuthenticate($keycloakUser); $user = $passport->getUser(); self::assertSame($existingUser, $user); self::assertSame('kc-456', $user->getKeycloakId()); } public function testAuthenticateUpdatesExistingKeycloakUser(): void { $existingUser = new User(); $existingUser->setEmail('old@example.com'); $existingUser->setFirstName('Old'); $existingUser->setLastName('Name'); $existingUser->setPassword('$2y$13$hashed'); $existingUser->setKeycloakId('kc-789'); $keycloakUser = $this->createKeycloakUser('kc-789', 'updated@example.com', 'New', 'Name', []); $repo = $this->createMock(EntityRepository::class); $repo->method('findOneBy')->willReturnCallback(function (array $criteria) use ($existingUser) { if (isset($criteria['keycloakId']) && 'kc-789' === $criteria['keycloakId']) { return $existingUser; } return null; }); $this->em->method('getRepository')->willReturn($repo); $this->em->expects(self::once())->method('flush'); $passport = $this->callAuthenticate($keycloakUser); $user = $passport->getUser(); self::assertSame($existingUser, $user); self::assertSame('updated@example.com', $user->getEmail()); self::assertSame('New', $user->getFirstName()); self::assertSame('Name', $user->getLastName()); } public function testAuthenticateMapsSuperadminGroupToRoleRoot(): void { $keycloakUser = $this->createKeycloakUser('kc-admin', 'admin@example.com', 'Admin', 'User', ['/superadmin']); $repo = $this->createMock(EntityRepository::class); $repo->method('findOneBy')->willReturn(null); $this->em->method('getRepository')->willReturn($repo); $this->em->expects(self::once())->method('persist'); $passport = $this->callAuthenticate($keycloakUser); $user = $passport->getUser(); self::assertContains('ROLE_ROOT', $user->getRoles()); } public function testAuthenticateIgnoresUnknownGroups(): void { $keycloakUser = $this->createKeycloakUser('kc-normal', 'normal@example.com', 'Normal', 'User', ['/editors', '/viewers']); $repo = $this->createMock(EntityRepository::class); $repo->method('findOneBy')->willReturn(null); $this->em->method('getRepository')->willReturn($repo); $this->em->expects(self::once())->method('persist'); $passport = $this->callAuthenticate($keycloakUser); $user = $passport->getUser(); self::assertSame(['ROLE_USER'], $user->getRoles()); } /** * @param list $groups */ private function createKeycloakUser(string $id, string $email, string $firstName, string $lastName, array $groups): KeycloakResourceOwner { return new KeycloakResourceOwner([ 'sub' => $id, 'email' => $email, 'given_name' => $firstName, 'family_name' => $lastName, 'groups' => $groups, ]); } private function callAuthenticate(KeycloakResourceOwner $keycloakUser): SelfValidatingPassport { $accessToken = $this->createMock(AccessToken::class); $accessToken->method('getToken')->willReturn('fake-token'); $oauthClient = $this->createMock(OAuth2ClientInterface::class); $oauthClient->method('fetchUserFromToken')->with($accessToken)->willReturn($keycloakUser); $this->clientRegistry->method('getClient')->with('keycloak')->willReturn($oauthClient); // Use reflection to mock fetchAccessToken from parent OAuth2Authenticator $authenticator = $this->getMockBuilder(KeycloakAuthenticator::class) ->setConstructorArgs([$this->clientRegistry, $this->em, $this->router]) ->onlyMethods(['fetchAccessToken']) ->getMock(); $authenticator->method('fetchAccessToken')->willReturn($accessToken); $request = new Request(); $request->attributes->set('_route', 'app_oauth_keycloak_check'); $passport = $authenticator->authenticate($request); // Resolve the UserBadge callback $passport->getUser(); return $passport; } }