diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6a77a2b..7816426 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,13 +14,15 @@ jobs: matrix: include: - php-version: '8.1' - symfony-version: '5.4.*' + symfony-version: '6.4.*' + - php-version: '8.4' + symfony-version: '6.4.*' - php-version: '8.2' - symfony-version: '5.4.*' - - php-version: '8.1' - symfony-version: '6.3.*' - - php-version: '8.2' - symfony-version: '6.3.*' + symfony-version: '7.4.*' + - php-version: '8.4' + symfony-version: '7.4.*' + - php-version: '8.4' + symfony-version: '8.0.*' steps: - name: "Checkout" uses: actions/checkout@v2 @@ -41,8 +43,8 @@ jobs: - name: "Setup env & install dependencies" uses: ./.github/actions/install with: - php-version: '8.1' - symfony-version: '6.1.*' + php-version: '8.4' + symfony-version: '8.0.*' - name: "Run static analyzis with phpstan/phpstan" run: vendor/bin/phpstan analyze @@ -55,8 +57,8 @@ jobs: - name: "Setup env & install dependencies" uses: ./.github/actions/install with: - php-version: '8.1' - symfony-version: '6.1.*' + php-version: '8.4' + symfony-version: '8.0.*' - name: "Run checkstyle with symplify/easy-coding-standard" run: vendor/bin/ecs @@ -69,8 +71,8 @@ jobs: - name: "Setup env & install dependencies" uses: ./.github/actions/install with: - php-version: '8.1' - symfony-version: '6.1.*' + php-version: '8.4' + symfony-version: '8.0.*' coverage-mode: 'xdebug' - name: "Run tests with phpunit/phpunit" env: diff --git a/composer.json b/composer.json index 374b746..6cffae7 100644 --- a/composer.json +++ b/composer.json @@ -11,15 +11,15 @@ "require": { "php": "^8.1", "ext-openssl": "*", - "symfony/framework-bundle": "^5.4|^6.0", - "doctrine/orm": "^2.7", - "doctrine/doctrine-bundle": "^2.0", + "symfony/framework-bundle": "^6.4|^7.4|^8.0", + "doctrine/common": "^3.0", + "doctrine/orm": "^3.0", + "doctrine/doctrine-bundle": "^2.0|^3.0", "yokai/dependency-injection": "^1.0" }, "require-dev": { "phpunit/phpunit": "^9.5", - "phpspec/prophecy-phpunit": "^2.0", - "symfony/yaml": "^5.4|^6.0", + "symfony/yaml": "^6.4|^7.4|^8.0", "phpstan/phpstan": "^1.7", "symplify/easy-coding-standard": "^11.3" }, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ca9d0c3..0ef85a8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,30 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:addDefaultsIfNotSet\\(\\)\\.$#" - count: 2 - path: src/DependencyInjection/Configuration.php - - - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" - count: 2 - path: src/DependencyInjection/Configuration.php - - - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:useAttributeAsKey\\(\\)\\.$#" - count: 1 - path: src/DependencyInjection/Configuration.php - - - - message: "#^Method Yokai\\\\SecurityTokenBundle\\\\Entity\\\\Token\\:\\:getLastUsage\\(\\) should return Yokai\\\\SecurityTokenBundle\\\\Entity\\\\TokenUsage\\|null but returns mixed\\.$#" - count: 1 - path: src/Entity/Token.php - - - - message: "#^Method Yokai\\\\SecurityTokenBundle\\\\Entity\\\\Token\\:\\:getUsages\\(\\) should return array\\ but returns array\\.$#" - count: 1 - path: src/Entity/Token.php - - message: "#^Property Yokai\\\\SecurityTokenBundle\\\\Entity\\\\Token\\:\\:\\$id is never written, only read\\.$#" count: 1 @@ -35,13 +10,3 @@ parameters: count: 1 path: src/Entity/TokenUsage.php - - - message: "#^Cannot cast mixed to string\\.$#" - count: 1 - path: src/Manager/ChainUserManager.php - - - - message: "#^Cannot cast mixed to string\\.$#" - count: 1 - path: src/Manager/DoctrineUserManager.php - diff --git a/phpstan.neon b/phpstan.neon index 98c6258..7f1a1f4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,7 +2,8 @@ includes: - phpstan-baseline.neon parameters: level: max - checkMissingIterableValueType: false - checkGenericClassInNonGenericObjectType: false + ignoreErrors: + - identifier: missingType.generics + - identifier: missingType.iterableValue paths: - src/ diff --git a/src/Archive/ArchivistInterface.php b/src/Archive/ArchivistInterface.php index 8d98550..9a808e6 100644 --- a/src/Archive/ArchivistInterface.php +++ b/src/Archive/ArchivistInterface.php @@ -18,5 +18,5 @@ interface ArchivistInterface * * @return integer */ - public function archive(string $purpose = null): int; + public function archive(string|null $purpose = null): int; } diff --git a/src/Archive/DeleteArchivist.php b/src/Archive/DeleteArchivist.php index aac11a1..7c99b6a 100644 --- a/src/Archive/DeleteArchivist.php +++ b/src/Archive/DeleteArchivist.php @@ -27,7 +27,7 @@ public function __construct(EntityRepository $tokenRepository) $this->tokenRepository = $tokenRepository; } - public function archive(string $purpose = null): int + public function archive(string|null $purpose = null): int { $builder = $this->tokenRepository->createQueryBuilder('token') ->delete($this->tokenRepository->getClassName(), 'token'); diff --git a/src/Entity/Token.php b/src/Entity/Token.php index ffa7931..772317a 100644 --- a/src/Entity/Token.php +++ b/src/Entity/Token.php @@ -70,7 +70,7 @@ class Token private $keepUntil; /** - * @var Collection + * @var Collection */ private $usages; @@ -192,15 +192,15 @@ public function getUsages(): array return $this->usages->toArray(); } - public function getLastUsage(): ?TokenUsage + public function getLastUsage(): TokenUsage|null { - return $this->usages->last(); + return $this->usages->last() ?: null; } /** * @throws LogicException */ - public function consume(array $information, DateTime $date = null): void + public function consume(array $information, DateTime|null $date = null): void { if ($this->isConsumed()) { throw new LogicException( diff --git a/src/Entity/TokenUsage.php b/src/Entity/TokenUsage.php index 6ad69e6..288ba12 100644 --- a/src/Entity/TokenUsage.php +++ b/src/Entity/TokenUsage.php @@ -31,7 +31,7 @@ class TokenUsage /** * @param array $information */ - public function __construct(Token $token, array $information, DateTime $createdAt = null) + public function __construct(Token $token, array $information, DateTime|null $createdAt = null) { $this->token = $token; $this->information = $information; diff --git a/src/EventDispatcher.php b/src/EventDispatcher.php index 5adc8eb..91e7f84 100644 --- a/src/EventDispatcher.php +++ b/src/EventDispatcher.php @@ -53,7 +53,7 @@ public function tokenCreated(Token $token): TokenCreatedEvent return $event; } - public function consumeToken(Token $token, DateTime $at = null, array $information = []): ConsumeTokenEvent + public function consumeToken(Token $token, DateTime|null $at = null, array $information = []): ConsumeTokenEvent { $this->eventDispatcher->dispatch( $event = new ConsumeTokenEvent($token, $at, $information) @@ -71,7 +71,6 @@ public function tokenConsumed(Token $token): TokenConsumedEvent return $event; } - public function tokenTotallyConsumed(Token $token): TokenTotallyConsumedEvent { $this->eventDispatcher->dispatch( diff --git a/src/Manager/ChainUserManager.php b/src/Manager/ChainUserManager.php index 46228df..2b445ed 100644 --- a/src/Manager/ChainUserManager.php +++ b/src/Manager/ChainUserManager.php @@ -108,10 +108,18 @@ private function getManagerForUser($user): UserManagerInterface $tries[] = get_class($manager); } - if (is_object($user) && !method_exists($user, '__toString')) { - $userAsString = sprintf('%s::%s', get_class($user), spl_object_hash($user)); + if (is_object($user)) { + if (!method_exists($user, '__toString')) { + $userAsString = sprintf('%s::%s', get_class($user), spl_object_hash($user)); + } else { + $userAsString = (string)$user; + } } else { - $userAsString = (string)$user; + if (is_scalar($user)) { + $userAsString = (string)$user; + } else { + $userAsString = get_debug_type($user); + } } throw new \InvalidArgumentException( diff --git a/src/Manager/DoctrineUserManager.php b/src/Manager/DoctrineUserManager.php index 13fbba0..aca3021 100644 --- a/src/Manager/DoctrineUserManager.php +++ b/src/Manager/DoctrineUserManager.php @@ -54,10 +54,8 @@ public function get(string $class, string $id) public function getClass($user): string { /** @var object $user */ - /** @var class-string $class */ - $class = ClassUtils::getClass($user); - return $class; + return ClassUtils::getClass($user); } public function getId($user): string @@ -71,7 +69,15 @@ public function getId($user): string throw new \InvalidArgumentException('Entities with composite ids are not supported'); } - return (string) reset($identifiers); + $identifier = reset($identifiers); + if (is_scalar($identifier) + || $identifier === null + || (is_object($identifier) && method_exists($identifier, '__toString')) + ) { + return (string)$identifier; + } + + throw new \InvalidArgumentException('Entities with non stringable ids are not supported'); } /** diff --git a/src/Manager/TokenManager.php b/src/Manager/TokenManager.php index 5fadd87..374c2fb 100644 --- a/src/Manager/TokenManager.php +++ b/src/Manager/TokenManager.php @@ -101,7 +101,7 @@ public function create(string $purpose, $user, array $payload = []): Token return $token; } - public function consume(Token $token, DateTime $at = null): void + public function consume(Token $token, DateTime|null $at = null): void { $event = $this->eventDispatcher->consumeToken($token, $at, $this->informationGuesser->get()); diff --git a/src/Manager/TokenManagerInterface.php b/src/Manager/TokenManagerInterface.php index 69d5dab..61931a0 100644 --- a/src/Manager/TokenManagerInterface.php +++ b/src/Manager/TokenManagerInterface.php @@ -48,7 +48,7 @@ public function create(string $purpose, $user, array $payload = []): Token; * @param Token $token The token to consume * @param DateTime|null $at The date/time at which the token was consumed (defaults to now) */ - public function consume(Token $token, DateTime $at = null): void; + public function consume(Token $token, DateTime|null $at = null): void; /** * Get the user associated to a token. diff --git a/src/Repository/DoctrineORMTokenRepository.php b/src/Repository/DoctrineORMTokenRepository.php index d64a0a9..42c0120 100644 --- a/src/Repository/DoctrineORMTokenRepository.php +++ b/src/Repository/DoctrineORMTokenRepository.php @@ -44,7 +44,7 @@ public function get(string $value, string $purpose): Token [ 'value' => $value, 'purpose' => $purpose, - ] + ], ); if (!$token instanceof Token) { @@ -67,7 +67,7 @@ public function findExisting(string $userClass, string $userId, string $purpose) 'userClass' => $userClass, 'userId' => $userId, 'purpose' => $purpose, - ] + ], ); if (!$token instanceof Token) { return null; @@ -81,14 +81,12 @@ public function findExisting(string $userClass, string $userId, string $purpose) public function create(Token $token): void { - $this->manager->persist($token); - $this->manager->flush($token); + $this->save($token); } public function update(Token $token): void { - $this->manager->persist($token); - $this->manager->flush($token); + $this->save($token); } public function exists(string $value, string $purpose): bool @@ -107,4 +105,10 @@ public function exists(string $value, string $purpose): bool return intval($result) > 0; } + + private function save(Token $token): void + { + $this->manager->persist($token); + $this->manager->flush(); + } } diff --git a/tests/Command/ArchiveTokenCommandTest.php b/tests/Command/ArchiveTokenCommandTest.php index aca9519..17d9871 100644 --- a/tests/Command/ArchiveTokenCommandTest.php +++ b/tests/Command/ArchiveTokenCommandTest.php @@ -4,8 +4,7 @@ namespace Yokai\SecurityTokenBundle\Tests\Command; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Console\Command\Command; @@ -19,10 +18,8 @@ */ class ArchiveTokenCommandTest extends KernelTestCase { - use ProphecyTrait; - /** - * @var ArchivistInterface|ObjectProphecy + * @var MockObject */ private $archivist; @@ -33,10 +30,10 @@ class ArchiveTokenCommandTest extends KernelTestCase protected function setUp(): void { - $this->archivist = $this->prophesize(ArchivistInterface::class); + $this->archivist = $this->createMock(ArchivistInterface::class); self::bootKernel(); - self::$kernel->getContainer()->set('yokai_security_token.archivist', $this->archivist->reveal()); + self::$kernel->getContainer()->set('yokai_security_token.archivist', $this->archivist); $this->application = new Application(self::$kernel); } @@ -75,8 +72,9 @@ public function it_archive_every_token_when_run_without_options_with_confirmatio { $command = $this->command(); - $this->archivist->archive(null) - ->shouldBeCalledTimes(1) + $this->archivist->expects(self::once()) + ->method('archive') + ->with(null) ->willReturn(10); $output = $this->runCommand($command); @@ -91,8 +89,9 @@ public function it_archive_partial_tokens_when_run_with_options(): void { $command = $this->command(); - $this->archivist->archive('init_password') - ->shouldBeCalledTimes(1) + $this->archivist->expects(self::once()) + ->method('archive') + ->with('init_password') ->willReturn(10); $output = $this->runCommand($command, ['purpose' => 'init_password']); diff --git a/tests/DependencyInjection/DependencyInjectionTest.php b/tests/DependencyInjection/DependencyInjectionTest.php index ffd8bea..11118fe 100644 --- a/tests/DependencyInjection/DependencyInjectionTest.php +++ b/tests/DependencyInjection/DependencyInjectionTest.php @@ -10,8 +10,6 @@ use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; use Generator; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ProphecySubjectInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; @@ -23,7 +21,6 @@ use Yokai\SecurityTokenBundle\Archive\ArchivistInterface; use Yokai\SecurityTokenBundle\Configuration\TokenConfiguration; use Yokai\SecurityTokenBundle\Factory\TokenFactoryInterface; -use Yokai\SecurityTokenBundle\Generator\OpenSslTokenGenerator; use Yokai\SecurityTokenBundle\Generator\TokenGeneratorInterface; use Yokai\SecurityTokenBundle\InformationGuesser\InformationGuesserInterface; use Yokai\SecurityTokenBundle\Manager\TokenManagerInterface; @@ -38,8 +35,6 @@ */ class DependencyInjectionTest extends TestCase { - use ProphecyTrait; - /** * @var ContainerBuilder */ @@ -60,7 +55,7 @@ protected function setUp(): void $this->container->setParameter('kernel.debug', true); $this->container->setParameter('kernel.bundles', $bundles); $this->container->setParameter('kernel.environment', 'test'); - $this->container->set('logger', $this->prophesize(LoggerInterface::class)->reveal()); + $this->container->set('logger', $this->createMock(LoggerInterface::class)); $this->container->setDefinition('doctrine', new Definition(ManagerRegistry::class)); $this->container->setDefinition('doctrine.orm.default_entity_manager', new Definition(EntityManager::class)); $this->container->setDefinition( @@ -82,7 +77,7 @@ protected function setUp(): void 'archivist_mock' => ArchivistInterface::class, ]; foreach ($mocks as $id => $class) { - $service = $this->prophesize($class)->reveal(); + $service = $this->createMock($class); $this->container->setDefinition($id, new Definition(get_class($service))); } @@ -173,12 +168,12 @@ public function configurationProvider(): Generator 'names.' . $format, [ 'security_password_init' => [ - 'generator' => OpenSslTokenGenerator::class, + 'generator' => TokenGeneratorInterface::class, 'duration' => '+2 days', 'usages' => 1, ], 'security_password_reset' => [ - 'generator' => OpenSslTokenGenerator::class, + 'generator' => TokenGeneratorInterface::class, 'duration' => '+2 days', 'usages' => 1, ], @@ -190,12 +185,12 @@ public function configurationProvider(): Generator 'full.' . $format, [ 'security_password_init' => [ - 'generator' => ProphecySubjectInterface::class, + 'generator' => TokenGeneratorInterface::class, 'duration' => '+1 month', 'usages' => 2, ], 'security_password_reset' => [ - 'generator' => ProphecySubjectInterface::class, + 'generator' => TokenGeneratorInterface::class, 'duration' => '+2 monthes', 'usages' => 3, ], diff --git a/tests/Factory/TokenFactoryTest.php b/tests/Factory/TokenFactoryTest.php index 54ceb9a..4b31da1 100644 --- a/tests/Factory/TokenFactoryTest.php +++ b/tests/Factory/TokenFactoryTest.php @@ -5,9 +5,8 @@ namespace Yokai\SecurityTokenBundle\Tests\Factory; use DateTime; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Yokai\SecurityTokenBundle\Configuration\TokenConfiguration; use Yokai\SecurityTokenBundle\Configuration\TokenConfigurationRegistry; use Yokai\SecurityTokenBundle\Entity\Token; @@ -24,28 +23,26 @@ */ class TokenFactoryTest extends TestCase { - use ProphecyTrait; - /** - * @var InformationGuesserInterface|ObjectProphecy + * @var MockObject */ private $informationGuesser; /** - * @var UserManagerInterface|ObjectProphecy + * @var MockObject */ private $userManager; /** - * @var TokenRepositoryInterface|ObjectProphecy + * @var MockObject */ private $repository; protected function setUp(): void { - $this->informationGuesser = $this->prophesize(InformationGuesserInterface::class); - $this->userManager = $this->prophesize(UserManagerInterface::class); - $this->repository = $this->prophesize(TokenRepositoryInterface::class); + $this->informationGuesser = $this->createMock(InformationGuesserInterface::class); + $this->userManager = $this->createMock(UserManagerInterface::class); + $this->repository = $this->createMock(TokenRepositoryInterface::class); } protected function tearDown(): void @@ -61,9 +58,9 @@ protected function factory(array $configuration): TokenFactory { return new TokenFactory( new TokenConfigurationRegistry($configuration), - $this->informationGuesser->reveal(), - $this->userManager->reveal(), - $this->repository->reveal() + $this->informationGuesser, + $this->userManager, + $this->repository ); } @@ -104,53 +101,37 @@ public function it_create_token_according_to_configuration(): void new TokenConfiguration('test-3', $generator3, '+2 minute', 1, '+1 month', true), ]; - $this->repository->findExisting('string', 'u1', 'test-1') - ->shouldNotBeCalled(); - $this->repository->findExisting('string', 'u2', 'test-2') - ->shouldNotBeCalled(); - $this->repository->findExisting('string', 'u3', 'test-3') - ->shouldBeCalledTimes(1) + $this->repository->expects(self::once()) + ->method('findExisting') + ->with('string', 'u3', 'test-3') ->willReturn($token3FromRepository); - $this->repository->exists('existtoken-1', 'test-1') - ->shouldBeCalledTimes(1) - ->willReturn(true); - $this->repository->exists('uniquetoken-1', 'test-1') - ->shouldBeCalledTimes(1) - ->willReturn(false); - $this->repository->exists('existtoken-2', 'test-2') - ->shouldBeCalledTimes(1) - ->willReturn(true); - $this->repository->exists('uniquetoken-2', 'test-2') - ->shouldBeCalledTimes(1) - ->willReturn(false); - $this->repository->exists('existtoken-3', 'test-3') - ->shouldNotBeCalled(); - $this->repository->exists('uniquetoken-3', 'test-3') - ->shouldNotBeCalled(); - - $this->userManager->getClass($user1) - ->shouldBeCalledTimes(1) - ->willReturn('string'); - $this->userManager->getClass($user2) - ->shouldBeCalledTimes(1) - ->willReturn('string'); - $this->userManager->getClass($user3) - ->shouldBeCalledTimes(1) - ->willReturn('string'); - - $this->userManager->getId($user1) - ->shouldBeCalledTimes(1) - ->willReturn('u1'); - $this->userManager->getId($user2) - ->shouldBeCalledTimes(1) - ->willReturn('u2'); - $this->userManager->getId($user3) - ->shouldBeCalledTimes(1) - ->willReturn('u3'); - - $this->informationGuesser->get() - ->shouldBeCalledTimes(2) + $this->repository->expects(self::exactly(4)) + ->method('exists') + ->willReturnCallback(function (string $purpose) { + return \substr($purpose, 0, 10) === 'existtoken'; + }); + + $this->userManager->expects(self::exactly(3)) + ->method('getClass') + ->with(self::isType('string')) + ->willReturnMap([ + [$user1, 'string'], + [$user2, 'string'], + [$user3, 'string'], + ]); + + $this->userManager->expects(self::exactly(3)) + ->method('getId') + ->with(self::isType('string')) + ->willReturnMap([ + [$user1, 'u1'], + [$user2, 'u2'], + [$user3, 'u3'], + ]); + + $this->informationGuesser->expects(self::exactly(2)) + ->method('get') ->willReturn(['some', 'precious', 'information']); $token1 = $this->factory($configuration)->create($user1, 'test-1'); diff --git a/tests/Manager/ChainUserManagerTest.php b/tests/Manager/ChainUserManagerTest.php index de54b36..bd8ba2e 100644 --- a/tests/Manager/ChainUserManagerTest.php +++ b/tests/Manager/ChainUserManagerTest.php @@ -5,10 +5,8 @@ namespace Yokai\SecurityTokenBundle\Tests\Manager; use InvalidArgumentException; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Yokai\SecurityTokenBundle\Manager\ChainUserManager; use Yokai\SecurityTokenBundle\Manager\UserManagerInterface; use Yokai\SecurityTokenBundle\Tests\Manager\Mock\UserDocument; @@ -21,8 +19,6 @@ */ class ChainUserManagerTest extends TestCase { - use ProphecyTrait; - private function manager($managers): ChainUserManager { return new ChainUserManager($managers); @@ -30,56 +26,50 @@ private function manager($managers): ChainUserManager private function entityManager(): UserManagerInterface { - /** @var UserManagerInterface|ObjectProphecy $manager */ - $manager = $this->prophesize(UserManagerInterface::class); - - $manager->supportsClass(Argument::any()) - ->will(function (array $args) { - return $args[0] === UserEntity::class; + /** @var MockObject $manager */ + $manager = $this->createMock(UserManagerInterface::class); + $manager->method('supportsClass') + ->willReturnCallback(function ($class) { + return $class === UserEntity::class; }); - - $manager->supportsUser(Argument::any()) - ->will(function (array $args) { - return $args[0] instanceof UserEntity; + $manager->method('supportsUser') + ->willReturnCallback(function ($object) { + return $object instanceof UserEntity; }); - - $manager->getClass(Argument::type(UserEntity::class)) + $manager->method('getClass') + ->with(self::isInstanceOf(UserEntity::class)) ->willReturn(UserEntity::class); - - $manager->getId(Argument::type(UserEntity::class)) + $manager->method('getId') ->willReturn('increment'); - - $manager->get(UserEntity::class, Argument::type('string')) + $manager->method('get') + ->with(UserEntity::class, self::isType('string')) ->willReturn(new UserEntity()); - return $manager->reveal(); + return $manager; } private function documentManager(): UserManagerInterface { - /** @var UserManagerInterface|ObjectProphecy $manager */ - $manager = $this->prophesize(UserManagerInterface::class); - - $manager->supportsClass(Argument::any()) - ->will(function (array $args) { - return $args[0] === UserDocument::class; + /** @var MockObject $manager */ + $manager = $this->createMock(UserManagerInterface::class); + $manager->method('supportsClass') + ->willReturnCallback(function ($class) { + return $class === UserDocument::class; }); - - $manager->supportsUser(Argument::any()) - ->will(function (array $args) { - return $args[0] instanceof UserDocument; + $manager->method('supportsUser') + ->willReturnCallback(function ($object) { + return $object instanceof UserDocument; }); - - $manager->getClass(Argument::type(UserDocument::class)) + $manager->method('getClass') + ->with(self::isInstanceOf(UserDocument::class)) ->willReturn(UserDocument::class); - - $manager->getId(Argument::type(UserDocument::class)) + $manager->method('getId') ->willReturn('uuid'); - - $manager->get(UserDocument::class, Argument::type('string')) + $manager->method('get') + ->with(UserDocument::class, self::isType('string')) ->willReturn(new UserDocument()); - return $manager->reveal(); + return $manager; } /** diff --git a/tests/Manager/DoctrineUserManagerTest.php b/tests/Manager/DoctrineUserManagerTest.php index 029e81d..784fb00 100644 --- a/tests/Manager/DoctrineUserManagerTest.php +++ b/tests/Manager/DoctrineUserManagerTest.php @@ -8,9 +8,8 @@ use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Yokai\SecurityTokenBundle\Manager\DoctrineUserManager; /** @@ -20,28 +19,26 @@ */ class DoctrineUserManagerTest extends TestCase { - use ProphecyTrait; - /** - * @var ManagerRegistry|ObjectProphecy + * @var MockObject */ private $registry; /** - * @var EntityManagerInterface|ObjectProphecy + * @var MockObject */ private $objectManager; /** - * @var ClassMetadata|ObjectProphecy + * @var MockObject */ private $classMetadata; protected function setUp(): void { - $this->registry = $this->prophesize(ManagerRegistry::class); - $this->objectManager = $this->prophesize(ObjectManager::class); - $this->classMetadata = $this->prophesize(ClassMetadata::class); + $this->registry = $this->createMock(ManagerRegistry::class); + $this->objectManager = $this->createMock(ObjectManager::class); + $this->classMetadata = $this->createMock(ClassMetadata::class); } protected function tearDown(): void @@ -55,7 +52,7 @@ protected function tearDown(): void protected function manager(): DoctrineUserManager { - return new DoctrineUserManager($this->registry->reveal()); + return new DoctrineUserManager($this->registry); } protected function user($id) @@ -82,8 +79,9 @@ public function it_supports_doctrine_entities(): void { $user = $this->user('jdoe'); - $this->registry->getManagerForClass(get_class($user)) - ->willReturn($this->objectManager->reveal()); + $this->registry->method('getManagerForClass') + ->with(get_class($user)) + ->willReturn($this->objectManager); $manager = $this->manager(); self::assertTrue($manager->supportsClass(get_class($user))); @@ -97,7 +95,8 @@ public function it_do_not_supports_objects_out_of_doctrine(): void { $user = $this->user('jdoe'); - $this->registry->getManagerForClass(get_class($user)) + $this->registry->method('getManagerForClass') + ->with(get_class($user)) ->willReturn(null); $manager = $this->manager(); @@ -112,12 +111,14 @@ public function it_get_user(): void { $expected = $this->user('jdoe'); - $this->registry->getManagerForClass(get_class($expected)) - ->shouldBeCalledTimes(1) - ->willReturn($this->objectManager->reveal()); + $this->registry->expects(self::once()) + ->method('getManagerForClass') + ->with(get_class($expected)) + ->willReturn($this->objectManager); - $this->objectManager->find(get_class($expected), 'jdoe') - ->shouldBeCalledTimes(1) + $this->objectManager->expects(self::once()) + ->method('find') + ->with(get_class($expected), 'jdoe') ->willReturn($expected); $user = $this->manager()->get(get_class($expected), 'jdoe'); @@ -144,16 +145,19 @@ public function it_get_user_id(): void { $expected = $this->user('jdoe'); - $this->registry->getManagerForClass(get_class($expected)) - ->shouldBeCalledTimes(1) - ->willReturn($this->objectManager->reveal()); + $this->registry->expects(self::once()) + ->method('getManagerForClass') + ->with(get_class($expected)) + ->willReturn($this->objectManager); - $this->objectManager->getClassMetadata(get_class($expected)) - ->shouldBeCalledTimes(1) - ->willReturn($this->classMetadata->reveal()); + $this->objectManager->expects(self::once()) + ->method('getClassMetadata') + ->with(get_class($expected)) + ->willReturn($this->classMetadata); - $this->classMetadata->getIdentifierValues($expected) - ->shouldBeCalledTimes(1) + $this->classMetadata->expects(self::once()) + ->method('getIdentifierValues') + ->with($expected) ->willReturn(['id' => 'jdoe']); $id = $this->manager()->getId($expected); diff --git a/tests/Manager/TokenManagerTest.php b/tests/Manager/TokenManagerTest.php index d54f3e0..c40702b 100644 --- a/tests/Manager/TokenManagerTest.php +++ b/tests/Manager/TokenManagerTest.php @@ -4,10 +4,8 @@ namespace Yokai\SecurityTokenBundle\Tests\Manager; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Yokai\SecurityTokenBundle\Entity\Token; use Yokai\SecurityTokenBundle\Event\ConsumeTokenEvent; @@ -36,40 +34,38 @@ */ class TokenManagerTest extends TestCase { - use ProphecyTrait; - /** - * @var TokenFactoryInterface|ObjectProphecy + * @var MockObject */ private $repository; /** - * @var InformationGuesserInterface|ObjectProphecy + * @var MockObject */ private $informationGuesser; /** - * @var UserManagerInterface|ObjectProphecy + * @var MockObject */ private $userManager; /** - * @var EventDispatcherInterface|ObjectProphecy + * @var MockObject */ private $eventDispatcher; protected function setUp(): void { - $this->factory = $this->prophesize(TokenFactoryInterface::class); - $this->repository = $this->prophesize(TokenRepositoryInterface::class); - $this->informationGuesser = $this->prophesize(InformationGuesserInterface::class); - $this->userManager = $this->prophesize(UserManagerInterface::class); - $this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $this->factory = $this->createMock(TokenFactoryInterface::class); + $this->repository = $this->createMock(TokenRepositoryInterface::class); + $this->informationGuesser = $this->createMock(InformationGuesserInterface::class); + $this->userManager = $this->createMock(UserManagerInterface::class); + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); } protected function tearDown(): void @@ -86,11 +82,11 @@ protected function tearDown(): void protected function manager(): TokenManager { return new TokenManager( - $this->factory->reveal(), - $this->repository->reveal(), - $this->informationGuesser->reveal(), - $this->userManager->reveal(), - new EventDispatcher($this->eventDispatcher->reveal()) + $this->factory, + $this->repository, + $this->informationGuesser, + $this->userManager, + new EventDispatcher($this->eventDispatcher) ); } @@ -101,17 +97,19 @@ public function it_dispatch_not_found_exceptions_on_get_token_from_repository(): { $this->expectException(TokenNotFoundException::class); - $this->repository->get('unique-token', 'forgot_password') - ->shouldBeCalledTimes(1) - ->willThrow(TokenNotFoundException::create('unique-token', 'forgot_password')); + $this->repository->expects(self::once()) + ->method('get') + ->with('unique-token', 'forgot_password') + ->willThrowException(TokenNotFoundException::create('unique-token', 'forgot_password')); - $notFoundEvent = Argument::allOf( - Argument::type(TokenNotFoundEvent::class), - Argument::which('getPurpose', 'forgot_password'), - Argument::which('getValue', 'unique-token') - ); - $this->eventDispatcher->dispatch($notFoundEvent) - ->shouldBeCalledTimes(1); + $notFoundEvent = self::callback(function ($event) { + return $event instanceof TokenNotFoundEvent + && $event->getPurpose() === 'forgot_password' + && $event->getValue() === 'unique-token'; + }); + $this->eventDispatcher->expects(self::once()) + ->method('dispatch') + ->with($notFoundEvent); $this->manager()->get('forgot_password', 'unique-token'); } @@ -123,17 +121,19 @@ public function it_dispatch_expired_exceptions_on_get_token_from_repository(): v { $this->expectException(TokenExpiredException::class); - $this->repository->get('unique-token', 'forgot_password') - ->shouldBeCalledTimes(1) - ->willThrow(TokenExpiredException::create('unique-token', 'forgot_password', new \DateTime())); + $this->repository->expects(self::once()) + ->method('get') + ->with('unique-token', 'forgot_password') + ->willThrowException(TokenExpiredException::create('unique-token', 'forgot_password', new \DateTime())); - $expiredEvent = Argument::allOf( - Argument::type(TokenExpiredEvent::class), - Argument::which('getPurpose', 'forgot_password'), - Argument::which('getValue', 'unique-token') - ); - $this->eventDispatcher->dispatch($expiredEvent) - ->shouldBeCalledTimes(1); + $expiredEvent = self::callback(function ($event) { + return $event instanceof TokenExpiredEvent + && $event->getPurpose() === 'forgot_password' + && $event->getValue() === 'unique-token'; + }); + $this->eventDispatcher->expects(self::once()) + ->method('dispatch') + ->with($expiredEvent); $this->manager()->get('forgot_password', 'unique-token'); } @@ -145,17 +145,19 @@ public function it_dispatch_used_exceptions_on_get_token_from_repository(): void { $this->expectException(TokenConsumedException::class); - $this->repository->get('unique-token', 'forgot_password') - ->shouldBeCalledTimes(1) - ->willThrow(TokenConsumedException::create('unique-token', 'forgot_password', 3)); + $this->repository->expects(self::once()) + ->method('get') + ->with('unique-token', 'forgot_password') + ->willThrowException(TokenConsumedException::create('unique-token', 'forgot_password', 3)); - $alreadyConsumedEvent = Argument::allOf( - Argument::type(TokenAlreadyConsumedEvent::class), - Argument::which('getPurpose', 'forgot_password'), - Argument::which('getValue', 'unique-token') - ); - $this->eventDispatcher->dispatch($alreadyConsumedEvent) - ->shouldBeCalledTimes(1); + $alreadyConsumedEvent = self::callback(function ($event) { + return $event instanceof TokenAlreadyConsumedEvent + && $event->getPurpose() === 'forgot_password' + && $event->getValue() === 'unique-token'; + }); + $this->eventDispatcher->expects(self::once()) + ->method('dispatch') + ->with($alreadyConsumedEvent); $this->manager()->get('forgot_password', 'unique-token'); } @@ -165,16 +167,18 @@ public function it_dispatch_used_exceptions_on_get_token_from_repository(): void */ public function it_get_token_from_repository(): void { - $this->repository->get('unique-token', 'forgot_password') - ->shouldBeCalledTimes(1) - ->willReturn($expected = $this->prophesize(Token::class)->reveal()); - - $retrievedEvent = Argument::allOf( - Argument::type(TokenRetrievedEvent::class), - Argument::which('getToken', $expected) - ); - $this->eventDispatcher->dispatch($retrievedEvent) - ->shouldBeCalledTimes(1); + $this->repository->expects(self::once()) + ->method('get') + ->with('unique-token', 'forgot_password') + ->willReturn($expected = $this->createMock(Token::class)); + + $retrievedEvent = self::callback(function ($event) use ($expected) { + return $event instanceof TokenRetrievedEvent + && $event->getToken() === $expected; + }); + $this->eventDispatcher->expects(self::once()) + ->method('dispatch') + ->with($retrievedEvent); $token = $this->manager()->get('forgot_password', 'unique-token'); @@ -198,28 +202,28 @@ public function it_create_unique_token(): void ['created', 'information'] ); - $this->factory->create('john-doe', 'forgot_password', ['payload', 'information']) - ->shouldBeCalledTimes(1) + $this->factory->expects(self::once()) + ->method('create') + ->with('john-doe', 'forgot_password', ['payload', 'information']) ->willReturn($expectedToken); - $this->repository->create($expectedToken) - ->shouldBeCalledTimes(1); + $this->repository->expects(self::once()) + ->method('create') + ->with($expectedToken); - $createEvent = Argument::allOf( - Argument::type(CreateTokenEvent::class), - Argument::which('getPurpose', 'forgot_password'), - Argument::which('getUser', 'john-doe'), - Argument::which('getPayload', ['payload', 'information']) - ); - $this->eventDispatcher->dispatch($createEvent) - ->shouldBeCalledTimes(1); + $events = self::callback(function ($event) use ($expectedToken) { + $isCreateTokenEvent = $event instanceof CreateTokenEvent + && $event->getPurpose() === 'forgot_password' + && $event->getUser() === 'john-doe' + && $event->getPayload() === ['payload', 'information']; + $isCreatedTokenEvent = $event instanceof TokenCreatedEvent + && $event->getToken() === $expectedToken; - $createdEvent = Argument::allOf( - Argument::type(TokenCreatedEvent::class), - Argument::which('getToken', $expectedToken) - ); - $this->eventDispatcher->dispatch($createdEvent) - ->shouldBeCalledTimes(1); + return $isCreateTokenEvent || $isCreatedTokenEvent; + }); + $this->eventDispatcher->expects(self::exactly(2)) + ->method('dispatch') + ->with($events); $token = $this->manager()->create('forgot_password', 'john-doe', ['payload', 'information']); @@ -233,34 +237,28 @@ public function it_consume_token(): void { $token = new Token('string', 'jdoe', 'unique-token', 'reset-password', '+1 day', '+1 month'); - $this->informationGuesser->get() - ->shouldBeCalledTimes(1) + $this->informationGuesser->expects(self::once()) + ->method('get') ->willReturn(['some', 'precious', 'information']); - $this->repository->update($token) - ->shouldBeCalledTimes(1); - - $consumeEvent = Argument::allOf( - Argument::type(ConsumeTokenEvent::class), - Argument::which('getToken', $token), - Argument::which('getInformation', ['some', 'precious', 'information']) - ); - $this->eventDispatcher->dispatch($consumeEvent) - ->shouldBeCalledTimes(1); - - $consumedEvent = Argument::allOf( - Argument::type(TokenConsumedEvent::class), - Argument::which('getToken', $token) - ); - $this->eventDispatcher->dispatch($consumedEvent) - ->shouldBeCalledTimes(1); - - $totallyConsumedEvent = Argument::allOf( - Argument::type(TokenTotallyConsumedEvent::class), - Argument::which('getToken', $token) - ); - $this->eventDispatcher->dispatch($totallyConsumedEvent) - ->shouldBeCalledTimes(1); + $this->repository->expects(self::once()) + ->method('update') + ->with($token); + + $events = self::callback(function ($event) use ($token) { + $isConsumeEvent = $event instanceof ConsumeTokenEvent + && $event->getToken() === $token + && $event->getInformation() === ['some', 'precious', 'information']; + $isConsumedEvent = $event instanceof TokenConsumedEvent + && $event->getToken() === $token; + $isTotallyConsumedEvent = $event instanceof TokenTotallyConsumedEvent + && $event->getToken() === $token; + + return $isConsumeEvent || $isConsumedEvent || $isTotallyConsumedEvent; + }); + $this->eventDispatcher->expects(self::exactly(3)) + ->method('dispatch') + ->with($events); $this->manager()->consume($token); @@ -279,8 +277,9 @@ public function it_extract_user_from_token(): void { $token = new Token('string', 'jdoe', 'unique-token', 'reset-password', '+1 day', '+1 month', 1, []); - $this->userManager->get('string', 'jdoe') - ->shouldBeCalledTimes(1) + $this->userManager->expects(self::once()) + ->method('get') + ->with('string', 'jdoe') ->willReturn('john doe'); $user = $this->manager()->getUser($token); diff --git a/tests/Repository/DoctrineORMTokenRepositoryTest.php b/tests/Repository/DoctrineORMTokenRepositoryTest.php index 72ae952..cba4510 100644 --- a/tests/Repository/DoctrineORMTokenRepositoryTest.php +++ b/tests/Repository/DoctrineORMTokenRepositoryTest.php @@ -6,9 +6,8 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Yokai\SecurityTokenBundle\Entity\Token; use Yokai\SecurityTokenBundle\Exception\TokenConsumedException; use Yokai\SecurityTokenBundle\Exception\TokenExpiredException; @@ -22,22 +21,20 @@ */ class DoctrineORMTokenRepositoryTest extends TestCase { - use ProphecyTrait; - /** - * @var EntityManager|ObjectProphecy + * @var MockObject */ private $manager; /** - * @var EntityRepository|ObjectProphecy + * @var MockObject */ private $repository; protected function setUp(): void { - $this->manager = $this->prophesize(EntityManager::class); - $this->repository = $this->prophesize(EntityRepository::class); + $this->manager = $this->createMock(EntityManager::class); + $this->repository = $this->createMock(EntityRepository::class); } protected function tearDown(): void @@ -50,7 +47,7 @@ protected function tearDown(): void protected function repository(): DoctrineORMTokenRepository { - return new DoctrineORMTokenRepository($this->manager->reveal(), $this->repository->reveal()); + return new DoctrineORMTokenRepository($this->manager, $this->repository); } /** @@ -60,8 +57,9 @@ public function it_throw_exception_if_token_not_found(): void { $this->expectException(TokenNotFoundException::class); - $this->repository->findOneBy(['value' => 'unique', 'purpose' => 'init_password']) - ->shouldBeCalledTimes(1) + $this->repository->expects(self::once()) + ->method('findOneBy') + ->with(['value' => 'unique', 'purpose' => 'init_password']) ->willReturn(null); $this->repository()->get('unique', 'init_password'); @@ -76,8 +74,9 @@ public function it_throw_exception_if_token_expired(): void $token = new Token('string', 'jdoe', 'unique', 'init_password', '-1 day', '+1 month', 1, []); - $this->repository->findOneBy(['value' => 'unique', 'purpose' => 'init_password']) - ->shouldBeCalledTimes(1) + $this->repository->expects(self::once()) + ->method('findOneBy') + ->with(['value' => 'unique', 'purpose' => 'init_password']) ->willReturn($token); $this->repository()->get('unique', 'init_password'); @@ -93,8 +92,9 @@ public function it_throw_exception_if_token_used_single_time(): void $token = new Token('string', 'jdoe', 'unique', 'init_password', '+1 day', '+1 month', 1); $token->consume(['info'], new \DateTime()); - $this->repository->findOneBy(['value' => 'unique', 'purpose' => 'init_password']) - ->shouldBeCalledTimes(1) + $this->repository->expects(self::once()) + ->method('findOneBy') + ->with(['value' => 'unique', 'purpose' => 'init_password']) ->willReturn($token); $this->repository()->get('unique', 'init_password'); @@ -111,8 +111,9 @@ public function it_throw_exception_if_token_used_multiple_times(): void $token->consume(['info'], new \DateTime()); $token->consume(['info'], new \DateTime()); - $this->repository->findOneBy(['value' => 'unique', 'purpose' => 'init_password']) - ->shouldBeCalledTimes(1) + $this->repository->expects(self::once()) + ->method('findOneBy') + ->with(['value' => 'unique', 'purpose' => 'init_password']) ->willReturn($token); $this->repository()->get('unique', 'init_password'); @@ -125,8 +126,9 @@ public function it_get_valid_token(): void { $token = new Token('string', 'jdoe', 'unique', 'init_password', '+1 day', '+1 month', 1, []); - $this->repository->findOneBy(['value' => 'unique', 'purpose' => 'init_password']) - ->shouldBeCalledTimes(1) + $this->repository->expects(self::once()) + ->method('findOneBy') + ->with(['value' => 'unique', 'purpose' => 'init_password']) ->willReturn($token); $got = $this->repository()->get('unique', 'init_password'); @@ -141,10 +143,17 @@ public function it_create_token(): void { $token = new Token('string', 'jdoe', 'unique', 'init_password', '+1 day', '+1 month', 1, []); - $this->manager->persist($token) - ->shouldBeCalledTimes(1); - $this->manager->flush($token) - ->shouldBeCalledTimes(1); + $this->manager->expects(self::once()) + ->method('persist') + ->with($token); + if ((new \ReflectionMethod(EntityManager::class, 'flush'))->getNumberOfParameters() > 0) { + $this->manager->expects(self::once()) + ->method('flush') + ->with($token); + } else { + $this->manager->expects(self::once()) + ->method('flush'); + } $this->repository()->create($token); } @@ -156,10 +165,17 @@ public function it_update_token(): void { $token = new Token('string', 'jdoe', 'unique', 'init_password', '+1 day', '+1 month', 1, []); - $this->manager->persist($token) - ->shouldBeCalledTimes(1); - $this->manager->flush($token) - ->shouldBeCalledTimes(1); + $this->manager->expects(self::once()) + ->method('persist') + ->with($token); + if ((new \ReflectionMethod(EntityManager::class, 'flush'))->getNumberOfParameters() > 0) { + $this->manager->expects(self::once()) + ->method('flush') + ->with($token); + } else { + $this->manager->expects(self::once()) + ->method('flush'); + } $this->repository()->update($token); } diff --git a/tests/app/config.yml b/tests/app/config.yml index fcb545b..09bd4fe 100644 --- a/tests/app/config.yml +++ b/tests/app/config.yml @@ -10,7 +10,6 @@ doctrine: charset: UTF8 path: "%kernel.project_dir%/app/db.db3" orm: - auto_generate_proxy_classes: "%kernel.debug%" naming_strategy: doctrine.orm.naming_strategy.underscore auto_mapping: true