entityManager = $this->createMock(EntityManagerInterface::class); $this->loggerService = $this->createMock(LoggerService::class); $this->service = new AccessTokenService( $this->entityManager, $this->loggerService ); } public function testRevokeUserTokensSuccess(): void { $userIdentifier = 'test@user.com'; // 1. Create Mock Tokens $token1 = $this->createMock(AccessToken::class); $token1->method('getIdentifier')->willReturn('token_1'); $token2 = $this->createMock(AccessToken::class); $token2->method('getIdentifier')->willReturn('token_2'); // 2. Mock Repository to return these tokens $repo = $this->createMock(EntityRepository::class); $repo->expects($this->once()) ->method('findBy') ->with(['userIdentifier' => $userIdentifier, 'revoked' => false]) ->willReturn([$token1, $token2]); $this->entityManager->expects($this->once()) ->method('getRepository') ->with(AccessToken::class) ->willReturn($repo); // 3. Expect revoke() to be called on EACH token $token1->expects($this->once())->method('revoke'); $token2->expects($this->once())->method('revoke'); // 4. Expect success logs $this->loggerService->expects($this->exactly(2)) ->method('logTokenRevocation') ->with( 'Access token revoked for user', $this->callback(function ($context) use ($userIdentifier) { return $context['user_identifier'] === $userIdentifier && in_array($context['token_id'], ['token_1', 'token_2']); }) ); // 5. Run $this->service->revokeUserTokens($userIdentifier); } public function testRevokeUserTokensHandlesException(): void { $userIdentifier = 'fail@user.com'; // 1. Create a Token that fails to revoke $tokenBad = $this->createMock(AccessToken::class); $tokenBad->method('getIdentifier')->willReturn('bad_token'); // Throw exception when revoke is called $tokenBad->expects($this->once()) ->method('revoke') ->willThrowException(new \Exception('DB Connection Lost')); // 2. Create a Token that works (to prove loop continues, if applicable) // Your code uses try-catch inside the loop, so it SHOULD continue. $tokenGood = $this->createMock(AccessToken::class); $tokenGood->method('getIdentifier')->willReturn('good_token'); $tokenGood->expects($this->once())->method('revoke'); // 3. Mock Repository $repo = $this->createMock(EntityRepository::class); $repo->method('findBy')->willReturn([$tokenBad, $tokenGood]); $this->entityManager->method('getRepository')->willReturn($repo); // 4. Expect Logger calls // Expect 1 Error log $this->loggerService->expects($this->once()) ->method('logError') ->with( 'Error revoking access token: DB Connection Lost', ['user_identifier' => $userIdentifier, 'token_id' => 'bad_token'] ); // Expect 1 Success log (for the good token) $this->loggerService->expects($this->once()) ->method('logTokenRevocation') ->with( 'Access token revoked for user', ['user_identifier' => $userIdentifier, 'token_id' => 'good_token'] ); // 5. Run $this->service->revokeUserTokens($userIdentifier); } public function testRevokeUserTokensDoesNothingIfNoneFound(): void { $userIdentifier = 'ghost@user.com'; $repo = $this->createMock(EntityRepository::class); $repo->method('findBy')->willReturn([]); // Empty array $this->entityManager->method('getRepository')->willReturn($repo); // Expect NO logs $this->loggerService->expects($this->never())->method('logTokenRevocation'); $this->loggerService->expects($this->never())->method('logError'); $this->service->revokeUserTokens($userIdentifier); } }