From d78fbcd7c922ea959f38c394619e7f58929c3da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Gon=C3=A7alves?= Date: Mon, 8 Apr 2024 18:26:13 +0100 Subject: [PATCH] Remove support for PHP 8.0 (#44) - Remove support for PHP 8.0 - Use Symfony 6.4 components for development - Update PHPUnit to 10.5 - Fix tests - Update documentation - Update ramsey/composer-install github action --- .github/workflows/auto_assign_owner.yml | 2 +- .github/workflows/continuous-integration.yml | 6 +- .gitignore | 9 +- composer.json | 16 +- docs/cache-cleaner.md | 34 +++- docs/projection-item.md | 5 +- docs/provider.md | 5 + docs/symfony.md | 5 +- phpunit.xml.dist | 16 +- .../AbstractCacheCleanerByTags.php | 6 +- src/CacheCleaner/CacheCleanerChain.php | 3 +- src/Provider/AbstractCachedProvider.php | 8 +- .../AbstractProjectionRepository.php | 10 +- .../SymfonyCacheProjectionRepository.php | 9 +- ...bstractDeflateCacheDecoratorSerializer.php | 2 +- .../Provider/JMSCacheSerializer.php | 2 +- .../Provider/SymfonyCacheSerializer.php | 2 +- src/Tag/Tag.php | 9 +- src/Tag/Tags.php | 20 +-- .../AbstractCacheCleanerTestCase.php | 8 +- .../AbstractCachedProviderTestCase.php | 29 ++-- tests/CacheCleaner/CacheCleanerChainTest.php | 2 +- tests/ProjectionItemIterableTraitTest.php | 4 +- tests/Provider/AbstractCachedProviderTest.php | 15 +- .../AbstractProjectionRepositoryTestCase.php | 148 +++++++++--------- .../SymfonyCacheProjectionRepositoryTest.php | 60 +++++-- .../AbstractCacheSerializerTestCase.php | 4 +- .../AbstractSymfonySerializerTestCase.php | 22 +++ .../Provider/SymfonyCacheSerializerTest.php | 14 +- .../SymfonyDeflatedCacheSerializerTest.php | 14 +- .../resources/igbinary-deflate.result | Bin 148 -> 143 bytes .../Provider/resources/igbinary.result | Bin 387 -> 239 bytes .../Provider/resources/php-deflate.result | Bin 177 -> 173 bytes .../Serializer/Provider/resources/php.result | Bin 460 -> 310 bytes tests/Stubs/CacheItem/CacheItemStub.php | 14 +- .../ProjectionItemIterableStub.php | 12 +- .../ProjectionItem/ProjectionItemStub.php | 2 +- tests/Stubs/Provider/CachedProviderStub.php | 2 +- tests/Tag/ProjectionTagGeneratorTest.php | 4 +- tests/Tag/TagTest.php | 18 ++- tests/Tag/TagsTest.php | 8 +- 41 files changed, 315 insertions(+), 234 deletions(-) create mode 100644 tests/Serializer/Provider/AbstractSymfonySerializerTestCase.php diff --git a/.github/workflows/auto_assign_owner.yml b/.github/workflows/auto_assign_owner.yml index df3daed..68eb43a 100644 --- a/.github/workflows/auto_assign_owner.yml +++ b/.github/workflows/auto_assign_owner.yml @@ -8,6 +8,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Auto assign owner - uses: danielswensson/auto-assign-owner-action@v1.0.2 + uses: danielswensson/auto-assign-owner-action@v1.0.6 with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index eda4022..8b5c088 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: php-version: - - 8.0 + - 8.1 dependencies: - highest @@ -32,7 +32,7 @@ jobs: coverage: xdebug - name: Install Composer Dependencies - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 with: dependency-versions: ${{ matrix.dependencies }} composer-options: "--prefer-stable" @@ -57,7 +57,7 @@ jobs: - uses: actions/download-artifact@v4 with: - name: build-8-highest-coverage + name: build-8.1-highest-coverage path: tests/.results/ - name: Fix Code Coverage Paths diff --git a/.gitignore b/.gitignore index 50a0dae..c308fb5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -.idea -tests/.results -vendor -composer.lock +.idea/ .php_cs.cache +composer.lock .phpunit.result.cache +.phpunit.cache +vendor/ +tests/.results/ diff --git a/composer.json b/composer.json index 2a5ba5c..a9bcfde 100644 --- a/composer.json +++ b/composer.json @@ -20,19 +20,19 @@ } ], "require": { - "php": ">=8.0" + "php": ">=8.1" }, "require-dev": { "ext-json": "*", "ext-igbinary": "*", "ext-zlib": "*", - "phpunit/phpunit": "^9.6", + "phpunit/phpunit": "^10.5", "kununu/scripts": ">=4.0", - "symfony/yaml": "^5.4", - "symfony/cache": "^5.4", + "symfony/yaml": "^6.4", + "symfony/cache": "^6.4", "jms/serializer": "^3.28", - "symfony/serializer": "^5.4", - "symfony/property-access": "^5.4" + "symfony/serializer": "^6.4", + "symfony/property-access": "^6.4" }, "suggest": { "ext-igbinary": "To use IgBinary cache serializers", @@ -53,8 +53,8 @@ } }, "scripts": { - "test": "phpunit --no-coverage tests", - "test-coverage": "XDEBUG_MODE=coverage phpunit --coverage-clover tests/.results/coverage.xml --coverage-html tests/.results/html/ tests" + "test": "phpunit --no-coverage --no-logging --no-progress", + "test-coverage": "XDEBUG_MODE=coverage phpunit" }, "scripts-descriptions": { "test": "Run all tests", diff --git a/docs/cache-cleaner.md b/docs/cache-cleaner.md index 3007add..f6f3900 100644 --- a/docs/cache-cleaner.md +++ b/docs/cache-cleaner.md @@ -27,9 +27,9 @@ So your cache cleaner class by tags should be instantiated with a `ProjectionRep ```php public function __construct( - private ProjectionRepositoryInterface $projectionRepository, - private LoggerInterface $logger, - private string $logLevel = LogLevel::INFO + private readonly ProjectionRepositoryInterface $projectionRepository, + private readonly LoggerInterface $logger, + private readonly string $logLevel = LogLevel::INFO ); abstract protected function getTags(): Tags; @@ -38,6 +38,11 @@ abstract protected function getTags(): Tags; Example: ```php + - + - - - src - + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" bootstrap="vendor/autoload.php" + colors="true" beStrictAboutChangesToGlobalState="true" cacheDirectory=".phpunit.cache" testdox="true"> + @@ -20,4 +17,9 @@ + + + src + + diff --git a/src/CacheCleaner/AbstractCacheCleanerByTags.php b/src/CacheCleaner/AbstractCacheCleanerByTags.php index 6c04d33..d2ab09f 100644 --- a/src/CacheCleaner/AbstractCacheCleanerByTags.php +++ b/src/CacheCleaner/AbstractCacheCleanerByTags.php @@ -11,9 +11,9 @@ abstract class AbstractCacheCleanerByTags implements CacheCleanerInterface { public function __construct( - private ProjectionRepositoryInterface $projectionRepository, - private LoggerInterface $logger, - private string $logLevel = LogLevel::INFO + private readonly ProjectionRepositoryInterface $projectionRepository, + private readonly LoggerInterface $logger, + private readonly string $logLevel = LogLevel::INFO ) { } diff --git a/src/CacheCleaner/CacheCleanerChain.php b/src/CacheCleaner/CacheCleanerChain.php index 784297c..01d7348 100644 --- a/src/CacheCleaner/CacheCleanerChain.php +++ b/src/CacheCleaner/CacheCleanerChain.php @@ -5,7 +5,8 @@ final class CacheCleanerChain implements CacheCleanerInterface { - private array $cacheCleaners; + /** @var CacheCleanerInterface[] */ + private readonly array $cacheCleaners; public function __construct(CacheCleanerInterface ...$cacheCleaners) { diff --git a/src/Provider/AbstractCachedProvider.php b/src/Provider/AbstractCachedProvider.php index 84ed685..638dbdb 100644 --- a/src/Provider/AbstractCachedProvider.php +++ b/src/Provider/AbstractCachedProvider.php @@ -15,9 +15,9 @@ abstract class AbstractCachedProvider private const DATA_KEY = 'data'; public function __construct( - private ProjectionRepositoryInterface $projectionRepository, - private LoggerInterface $logger, - private string $logLevel = LogLevel::INFO + private readonly ProjectionRepositoryInterface $projectionRepository, + private readonly LoggerInterface $logger, + private readonly string $logLevel = LogLevel::INFO ) { } @@ -83,7 +83,7 @@ protected function invalidateCacheItemByKey(ProjectionItemIterableInterface $pro private function log(string $message, string $cacheKey, mixed $data = null): void { - $this->logger() + $this->logger ->log( $this->logLevel, $message, diff --git a/src/Repository/AbstractProjectionRepository.php b/src/Repository/AbstractProjectionRepository.php index 8cfeadd..e858f8c 100644 --- a/src/Repository/AbstractProjectionRepository.php +++ b/src/Repository/AbstractProjectionRepository.php @@ -14,8 +14,8 @@ abstract class AbstractProjectionRepository implements ProjectionRepositoryInterface { public function __construct( - protected CacheItemPoolInterface $cachePool, - protected CacheSerializerInterface $serializer + protected readonly CacheItemPoolInterface $cachePool, + protected readonly CacheSerializerInterface $serializer ) { } @@ -48,11 +48,11 @@ public function get(ProjectionItemInterface $item): ?ProjectionItemInterface { $cacheItem = $this->cachePool->getItem($item->getKey()); - if (!$cacheItem->isHit()) { - return null; + if ($cacheItem->isHit()) { + return $this->serializer->deserialize($cacheItem->get(), $item::class); } - return $this->serializer->deserialize($cacheItem->get(), $item::class); + return null; } public function delete(ProjectionItemInterface $item): void diff --git a/src/Repository/SymfonyCacheProjectionRepository.php b/src/Repository/SymfonyCacheProjectionRepository.php index 2fc2429..8c26d27 100644 --- a/src/Repository/SymfonyCacheProjectionRepository.php +++ b/src/Repository/SymfonyCacheProjectionRepository.php @@ -19,7 +19,7 @@ public function __construct(TagAwareAdapterInterface $cachePool, CacheSerializer public function deleteByTags(Tags $tags): void { - if (!$this->cachePool->invalidateTags($tags->raw())) { + if (!$this->getCachePool()->invalidateTags($tags->raw())) { throw new ProjectionException('Not possible to delete projection items on cache pool based on tag'); } } @@ -31,4 +31,11 @@ protected function createCacheItem(ProjectionItemInterface $item): CacheItemInte return $cacheItem; } + + private function getCachePool(): TagAwareAdapterInterface + { + assert($this->cachePool instanceof TagAwareAdapterInterface); + + return $this->cachePool; + } } diff --git a/src/Serializer/Provider/AbstractDeflateCacheDecoratorSerializer.php b/src/Serializer/Provider/AbstractDeflateCacheDecoratorSerializer.php index 5d0c190..3fb1f27 100644 --- a/src/Serializer/Provider/AbstractDeflateCacheDecoratorSerializer.php +++ b/src/Serializer/Provider/AbstractDeflateCacheDecoratorSerializer.php @@ -7,7 +7,7 @@ abstract class AbstractDeflateCacheDecoratorSerializer implements CacheSerializerInterface { - public function __construct(private CacheSerializerInterface $serializer) + public function __construct(private readonly CacheSerializerInterface $serializer) { } diff --git a/src/Serializer/Provider/JMSCacheSerializer.php b/src/Serializer/Provider/JMSCacheSerializer.php index 5c4806a..daba40a 100644 --- a/src/Serializer/Provider/JMSCacheSerializer.php +++ b/src/Serializer/Provider/JMSCacheSerializer.php @@ -10,7 +10,7 @@ final class JMSCacheSerializer implements CacheSerializerInterface { private const SERIALIZER_FORMAT = 'json'; - public function __construct(private SerializerInterface $serializer) + public function __construct(private readonly SerializerInterface $serializer) { } diff --git a/src/Serializer/Provider/SymfonyCacheSerializer.php b/src/Serializer/Provider/SymfonyCacheSerializer.php index 4aeb744..31040de 100644 --- a/src/Serializer/Provider/SymfonyCacheSerializer.php +++ b/src/Serializer/Provider/SymfonyCacheSerializer.php @@ -10,7 +10,7 @@ final class SymfonyCacheSerializer implements CacheSerializerInterface { private const SERIALIZER_FORMAT = 'json'; - public function __construct(private SerializerInterface $serializer) + public function __construct(private readonly SerializerInterface $serializer) { } diff --git a/src/Tag/Tag.php b/src/Tag/Tag.php index cc5a6d1..e2e8104 100644 --- a/src/Tag/Tag.php +++ b/src/Tag/Tag.php @@ -7,22 +7,17 @@ final class Tag implements Stringable { - public function __construct(private string $tag) + public function __construct(public readonly string $tag) { } public function __toString(): string - { - return $this->value(); - } - - public function value(): string { return $this->tag; } public function equals(Tag $other): bool { - return $other->value() === $this->value(); + return $other->tag === $this->tag; } } diff --git a/src/Tag/Tags.php b/src/Tag/Tags.php index 9099c38..3a9b118 100644 --- a/src/Tag/Tags.php +++ b/src/Tag/Tags.php @@ -5,13 +5,11 @@ final class Tags { - private array $tags = []; + private readonly array $tags; public function __construct(Tag ...$tags) { - foreach ($tags as $tag) { - $this->add($tag); - } + $this->tags = $this->createTags(...$tags); } public function raw(): array @@ -19,14 +17,16 @@ public function raw(): array return array_keys($this->tags); } - private function add(Tag $tag): void + private function createTags(Tag ...$tags): array { - $value = $tag->value(); - - if (isset($this->tags[$value])) { - return; + $values = []; + foreach ($tags as $tag) { + if (isset($values[$tag->tag])) { + continue; + } + $values[$tag->tag] = true; } - $this->tags[$value] = true; + return $values; } } diff --git a/src/TestCase/CacheCleaner/AbstractCacheCleanerTestCase.php b/src/TestCase/CacheCleaner/AbstractCacheCleanerTestCase.php index 6faaa61..9f53591 100644 --- a/src/TestCase/CacheCleaner/AbstractCacheCleanerTestCase.php +++ b/src/TestCase/CacheCleaner/AbstractCacheCleanerTestCase.php @@ -25,13 +25,13 @@ abstract class AbstractCacheCleanerTestCase extends TestCase public function testCacheCleaner(): void { $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('invalidateTags') ->with(static::TAGS) ->willReturn(true); $this->logger - ->expects($this->once()) + ->expects(self::once()) ->method('log') ->with( LogLevel::INFO, @@ -45,13 +45,13 @@ public function testCacheCleaner(): void public function testCacheCleanerFail(): void { $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('invalidateTags') ->with(static::TAGS) ->willReturn(false); $this->logger - ->expects($this->once()) + ->expects(self::once()) ->method('log') ->with( LogLevel::INFO, diff --git a/src/TestCase/Provider/AbstractCachedProviderTestCase.php b/src/TestCase/Provider/AbstractCachedProviderTestCase.php index 05dda5c..4e60f5c 100644 --- a/src/TestCase/Provider/AbstractCachedProviderTestCase.php +++ b/src/TestCase/Provider/AbstractCachedProviderTestCase.php @@ -6,7 +6,8 @@ use Kununu\Projections\ProjectionItemIterableInterface; use Kununu\Projections\ProjectionRepositoryInterface; use Kununu\Projections\Provider\AbstractCachedProvider; -use PHPUnit\Framework\MockObject\Generator; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\MockObject\MockBuilder; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; @@ -18,9 +19,9 @@ abstract class AbstractCachedProviderTestCase extends TestCase protected MockObject|ProjectionRepositoryInterface|null $projectionRepository = null; protected MockObject|LoggerInterface|null $logger = null; - /** @dataProvider getAndCacheDataDataProvider */ + #[DataProvider('getAndCacheDataDataProvider')] public function testGetAndCacheData( - $originalProvider, + mixed $originalProvider, string $method, array $args, ProjectionItemIterableInterface $item, @@ -31,7 +32,7 @@ public function testGetAndCacheData( ): void { // Get from cache ($repository = $this->getProjectionRepository()) - ->expects($this->once()) + ->expects(self::once()) ->method('get') ->with($item) ->willReturn($projectedItem); @@ -46,19 +47,19 @@ public function testGetAndCacheData( if ($expectedResult && $itemToProject) { // Add data to the cache $repository - ->expects($this->once()) + ->expects(self::once()) ->method('add') ->with($itemToProject); } else { $repository - ->expects($this->never()) + ->expects(self::never()) ->method('add'); } } $result = call_user_func_array([$this->getProvider($originalProvider), $method], $args); - $this->assertEquals($expectedResult, $result); + self::assertEquals($expectedResult, $result); } public static function getAndCacheDataDataProvider(): array @@ -84,15 +85,11 @@ protected static function createExternalProvider( bool $expected, ?iterable $data ): MockObject { - $provider = (new Generator()) - ->getMock( - type: $providerClass, - methods: [$method], - callOriginalConstructor: false, - callOriginalClone: false, - cloneArguments: false, - allowMockingUnknownTypes: false, - ); + $provider = (new MockBuilder(new static(sprintf('%s::%s', $providerClass, $method)), $providerClass)) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->onlyMethods([$method]) + ->getMock(); $invocationMocker = $provider ->expects($expected ? self::once() : self::never()) diff --git a/tests/CacheCleaner/CacheCleanerChainTest.php b/tests/CacheCleaner/CacheCleanerChainTest.php index af3fc82..841c500 100644 --- a/tests/CacheCleaner/CacheCleanerChainTest.php +++ b/tests/CacheCleaner/CacheCleanerChainTest.php @@ -25,7 +25,7 @@ private function createCacheCleaner(): CacheCleanerInterface $cleaner = $this->createMock(CacheCleanerInterface::class); $cleaner - ->expects($this->once()) + ->expects(self::once()) ->method('clear'); return $cleaner; diff --git a/tests/ProjectionItemIterableTraitTest.php b/tests/ProjectionItemIterableTraitTest.php index 4f68828..e6cb0c8 100644 --- a/tests/ProjectionItemIterableTraitTest.php +++ b/tests/ProjectionItemIterableTraitTest.php @@ -30,7 +30,7 @@ public function getTags(): Tags $validClass->storeData([1, 2, 3]); - $this->assertEquals([1, 2, 3], $validClass->data()); + self::assertEquals([1, 2, 3], $validClass->data()); $iterator = new ArrayIterator(); $iterator->append('a'); @@ -38,7 +38,7 @@ public function getTags(): Tags $iterator->append(5); $validClass->storeData($iterator); - $this->assertEquals(['a', 'b', 5], $validClass->data()); + self::assertEquals(['a', 'b', 5], $validClass->data()); $invalidClass = new class() { use ProjectionItemIterableTrait; diff --git a/tests/Provider/AbstractCachedProviderTest.php b/tests/Provider/AbstractCachedProviderTest.php index b55c1d7..35420e4 100644 --- a/tests/Provider/AbstractCachedProviderTest.php +++ b/tests/Provider/AbstractCachedProviderTest.php @@ -95,7 +95,7 @@ public function testInvalidateCacheItemByKey(): void $provider = $this->getProvider(new ProviderStub()); $this->getLogger() - ->expects($this->once()) + ->expects(self::once()) ->method('log') ->with( LogLevel::INFO, @@ -104,7 +104,7 @@ public function testInvalidateCacheItemByKey(): void ); $this->getProjectionRepository() - ->expects($this->once()) + ->expects(self::once()) ->method('delete') ->with(new ProjectionItemIterableStub(self::ID_1)); @@ -122,7 +122,7 @@ public function testCreateExternalProviderReturningData(): void data: self::DATA ); - $this->assertEquals(self::DATA, $externalProvider->getData(self::ID_1)); + self::assertEquals(self::DATA, $externalProvider->getData(self::ID_1)); } public function testCreateExternalProviderNotReturningData(): void @@ -138,14 +138,17 @@ public function testCreateExternalProviderNotReturningData(): void $this->expectException(ExpectationFailedException::class); - $this->assertNull($externalProvider->getData(self::ID_1)); + self::assertNull($externalProvider->getData(self::ID_1)); } public function testGetAndCacheDataDataProvider(): void { - $keys = array_map(fn($key) => sprintf('getData_%s', $key), array_keys(self::getDataDataProvider())); + $keys = array_map( + static fn($key): string => sprintf('getData_%s', $key), + array_keys(self::getDataDataProvider()) + ); - $this->assertEquals(array_combine($keys, self::getDataDataProvider()), self::getAndCacheDataDataProvider()); + self::assertEquals(array_combine($keys, self::getDataDataProvider()), self::getAndCacheDataDataProvider()); } protected function getProvider(mixed $originalProvider): CachedProviderStub diff --git a/tests/Repository/AbstractProjectionRepositoryTestCase.php b/tests/Repository/AbstractProjectionRepositoryTestCase.php index a4175ed..a16139d 100644 --- a/tests/Repository/AbstractProjectionRepositoryTestCase.php +++ b/tests/Repository/AbstractProjectionRepositoryTestCase.php @@ -11,137 +11,138 @@ use Kununu\Projections\Tests\Stubs\ProjectionItem\ProjectionItemStub; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; abstract class AbstractProjectionRepositoryTestCase extends TestCase { - protected const ID = 'id_item'; - protected const KEY = 'test_id_item'; - protected const SERIALIZED = 'serialized_projection_item_dummy'; + private const ID = 'id_item'; + private const KEY = 'test_id_item'; + private const SERIALIZED = 'serialized_projection_item_dummy'; protected MockObject|CacheItemPoolInterface|null $cachePool = null; protected MockObject|CacheSerializerInterface $serializer; public function testAdd(): void { - $cacheItemStub = new CacheItemStub(self::ID); + $cacheItem = $this->adaptCacheItem(new CacheItemStub(self::ID)); $item = new ProjectionItemStub(self::ID); - $this->cachePool - ->expects($this->once()) + $this->getCachePool() + ->expects(self::once()) ->method('getItem') ->with(self::KEY) - ->willReturn($cacheItemStub); + ->willReturn($cacheItem); - $this->cachePool - ->expects($this->once()) + $this->getCachePool() + ->expects(self::once()) ->method('save') ->willReturn(true); $this->serializer - ->expects($this->once()) + ->expects(self::once()) ->method('serialize') ->with($item) ->willReturn(self::SERIALIZED); $this->getProjectionRepository()->add($item); - $this->assertEquals(self::SERIALIZED, $cacheItemStub->get()); - $this->extraAssertionsForAdd($cacheItemStub); + self::assertEquals(self::SERIALIZED, $cacheItem->get()); + $this->extraAssertionsForAdd($cacheItem); } public function testAddIterable(): void { - $cacheItemStub = new CacheItemStub(self::ID); + $cacheItem = $this->adaptCacheItem(new CacheItemStub(self::ID)); $item = (new ProjectionItemIterableStub(self::ID, 'itn'))->storeData(['id' => 'beiga', 'value' => 1000]); $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('getItem') ->with('test_iterable_id_item') - ->willReturn($cacheItemStub); + ->willReturn($cacheItem); $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('save') ->willReturn(true); $this->serializer - ->expects($this->once()) + ->expects(self::once()) ->method('serialize') ->with($item) ->willReturnCallback(fn(ProjectionItemIterableStub $item): string => json_encode([ 'key' => $item->getKey(), - 'stuff' => $item->stuff(), + 'stuff' => $item->stuff, 'data' => $item->data(), ])); $this->getProjectionRepository()->add($item); - $this->assertEquals( + self::assertEquals( '{"key":"test_iterable_id_item","stuff":"itn","data":{"id":"beiga","value":1000}}', - $cacheItemStub->get() + $cacheItem->get() ); - $this->extraAssertionsForAddIterable($cacheItemStub); + $this->extraAssertionsForAddIterable($cacheItem); } - public function testWhenAddFails(): void + public function testAddDeferred(): void { - $this->expectException(ProjectionException::class); - $this->expectExceptionMessage('Not possible to add projection item on cache pool'); - + $cacheItem = $this->adaptCacheItem(new CacheItemStub(self::ID)); $item = new ProjectionItemStub(self::ID); $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('getItem') ->with(self::KEY) - ->willReturn(new CacheItemStub(self::KEY)); + ->willReturn($cacheItem); $this->cachePool - ->expects($this->once()) - ->method('save') - ->willReturn(false); + ->expects(self::never()) + ->method('save'); + + $this->cachePool + ->expects(self::once()) + ->method('saveDeferred') + ->willReturn(true); $this->serializer - ->expects($this->once()) + ->expects(self::once()) ->method('serialize') ->with($item) ->willReturn(self::SERIALIZED); - $this->getProjectionRepository()->add($item); + $this->getProjectionRepository()->addDeferred($item); + + self::assertEquals(self::SERIALIZED, $cacheItem->get()); + $this->extraAssertionsForAddDeferred($cacheItem); } - public function testAddDeferred(): void + public function testWhenAddFails(): void { - $cacheItemStub = new CacheItemStub(self::ID); + $this->expectException(ProjectionException::class); + $this->expectExceptionMessage('Not possible to add projection item on cache pool'); + $item = new ProjectionItemStub(self::ID); $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('getItem') ->with(self::KEY) - ->willReturn($cacheItemStub); - - $this->cachePool - ->expects($this->never()) - ->method('save'); + ->willReturn($this->adaptCacheItem(new CacheItemStub(self::KEY))); $this->cachePool - ->expects($this->once()) - ->method('saveDeferred') - ->willReturn(true); + ->expects(self::once()) + ->method('save') + ->willReturn(false); $this->serializer - ->expects($this->once()) + ->expects(self::once()) ->method('serialize') ->with($item) ->willReturn(self::SERIALIZED); - $this->getProjectionRepository()->addDeferred($item); - - $this->assertEquals(self::SERIALIZED, $cacheItemStub->get()); - $this->extraAssertionsForAddDeferred($cacheItemStub); + $this->getProjectionRepository()->add($item); } public function testWhenAddDeferredFails(): void @@ -152,22 +153,22 @@ public function testWhenAddDeferredFails(): void $item = new ProjectionItemStub(self::ID); $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('getItem') ->with(self::KEY) - ->willReturn(new CacheItemStub(self::KEY)); + ->willReturn($this->adaptCacheItem(new CacheItemStub(self::KEY))); $this->cachePool - ->expects($this->never()) + ->expects(self::never()) ->method('save'); $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('saveDeferred') ->willReturn(false); $this->serializer - ->expects($this->once()) + ->expects(self::once()) ->method('serialize') ->with($item) ->willReturn(self::SERIALIZED); @@ -179,45 +180,45 @@ public function testGetExistentItem(): void { $projectionItem = new ProjectionItemStub(self::ID); $projectionItemOnCache = new ProjectionItemStub(self::ID); - $cacheItem = (new CacheItemStub(self::ID))->setHit()->set(self::SERIALIZED); + $cacheItem = $this->adaptCacheItem((new CacheItemStub(self::ID))->setHit()->set(self::SERIALIZED)); $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('getItem') ->with($projectionItem->getKey()) ->willReturn($cacheItem); $this->serializer - ->expects($this->once()) + ->expects(self::once()) ->method('deserialize') ->with(self::SERIALIZED, $projectionItem::class) ->willReturn($projectionItemOnCache); - $this->assertEquals($projectionItemOnCache, $this->getProjectionRepository()->get($projectionItem)); + self::assertEquals($projectionItemOnCache, $this->getProjectionRepository()->get($projectionItem)); } public function testGetNonExistentItem(): void { $projectionItem = new ProjectionItemStub(self::ID); - $cacheItem = (new CacheItemStub(self::ID))->setNotHit(); + $cacheItem = $this->adaptCacheItem((new CacheItemStub(self::ID))->setNotHit()); $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('getItem') ->with($projectionItem->getKey()) ->willReturn($cacheItem); $this->serializer - ->expects($this->never()) + ->expects(self::never()) ->method('deserialize'); - $this->assertNull($this->getProjectionRepository()->get($projectionItem)); + self::assertNull($this->getProjectionRepository()->get($projectionItem)); } public function testDelete(): void { - $this->cachePool - ->expects($this->once()) + $this->getCachePool() + ->expects(self::once()) ->method('deleteItem') ->willReturn(true); @@ -230,7 +231,7 @@ public function testWhenDeleteFails(): void $this->expectExceptionMessage('Not possible to delete projection item on cache pool'); $this->cachePool - ->expects($this->once()) + ->expects(self::once()) ->method('deleteItem') ->willReturn(false); @@ -239,8 +240,8 @@ public function testWhenDeleteFails(): void public function testFlush(): void { - $this->cachePool - ->expects($this->once()) + $this->getCachePool() + ->expects(self::once()) ->method('commit') ->willReturn(true); @@ -252,8 +253,8 @@ public function testWhenFlushFails(): void $this->expectException(ProjectionException::class); $this->expectExceptionMessage('Not possible to add projection items on cache pool by flush'); - $this->cachePool - ->expects($this->once()) + $this->getCachePool() + ->expects(self::once()) ->method('commit') ->willReturn(false); @@ -270,15 +271,20 @@ protected function setUp(): void $this->serializer = $this->createMock(CacheSerializerInterface::class); } - protected function extraAssertionsForAdd(CacheItemStub $cacheItemStub): void + protected function extraAssertionsForAdd(CacheItemInterface $cacheItem): void + { + } + + protected function extraAssertionsForAddIterable(CacheItemInterface $cacheItem): void { } - protected function extraAssertionsForAddIterable(CacheItemStub $cacheItemStub): void + protected function extraAssertionsForAddDeferred(CacheItemInterface $cacheItem): void { } - protected function extraAssertionsForAddDeferred(CacheItemStub $cacheItemStub): void + protected function adaptCacheItem(CacheItemInterface $cacheItem): CacheItemInterface { + return $cacheItem; } } diff --git a/tests/Repository/SymfonyCacheProjectionRepositoryTest.php b/tests/Repository/SymfonyCacheProjectionRepositoryTest.php index 0c10239..4069e74 100644 --- a/tests/Repository/SymfonyCacheProjectionRepositoryTest.php +++ b/tests/Repository/SymfonyCacheProjectionRepositoryTest.php @@ -3,21 +3,23 @@ namespace Kununu\Projections\Tests\Repository; +use Closure; use Kununu\Projections\Exception\ProjectionException; use Kununu\Projections\ProjectionRepositoryInterface; use Kununu\Projections\Repository\SymfonyCacheProjectionRepository; use Kununu\Projections\Tag\Tag; use Kununu\Projections\Tag\Tags; -use Kununu\Projections\Tests\Stubs\CacheItem\CacheItemStub; use PHPUnit\Framework\MockObject\MockObject; +use Psr\Cache\CacheItemInterface; use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; +use Symfony\Component\Cache\CacheItem; final class SymfonyCacheProjectionRepositoryTest extends AbstractProjectionRepositoryTestCase { public function testDeleteByTags(): void { - $this->cachePool - ->expects($this->once()) + $this->getCachePool() + ->expects(self::once()) ->method('invalidateTags') ->with(['tag_1', 'tag_2']) ->willReturn(true); @@ -30,27 +32,27 @@ public function testWhenDeleteByTagsFails(): void $this->expectException(ProjectionException::class); $this->expectExceptionMessage('Not possible to delete projection items on cache pool based on tag'); - $this->cachePool - ->expects($this->once()) + $this->getCachePool() + ->expects(self::once()) ->method('invalidateTags') ->willReturn(false); $this->getProjectionRepository()->deleteByTags(new Tags()); } - protected function extraAssertionsForAdd(CacheItemStub $cacheItemStub): void + protected function extraAssertionsForAdd(CacheItemInterface $cacheItem): void { - $this->assertTags($cacheItemStub); + $this->assertTags($cacheItem); } - protected function extraAssertionsForAddIterable(CacheItemStub $cacheItemStub): void + protected function extraAssertionsForAddIterable(CacheItemInterface $cacheItem): void { - $this->assertTags($cacheItemStub); + $this->assertTags($cacheItem); } - protected function extraAssertionsForAddDeferred(CacheItemStub $cacheItemStub): void + protected function extraAssertionsForAddDeferred(CacheItemInterface $cacheItem): void { - $this->assertTags($cacheItemStub); + $this->assertTags($cacheItem); } protected function getCachePool(): MockObject|TagAwareAdapterInterface @@ -67,8 +69,40 @@ protected function getProjectionRepository(): ProjectionRepositoryInterface return new SymfonyCacheProjectionRepository($this->getCachePool(), $this->serializer); } - private function assertTags(CacheItemStub $cacheItemStub): void + /** + * In symfony/cache 6.4, AdapterInterface is defined to return a concrete implementation of CacheItemInterface, + * so we need an adapter from to create a Symfony CacheItem instance from CacheItemStub. + */ + protected function adaptCacheItem(CacheItemInterface $cacheItem): CacheItem { - $this->assertEquals(['test', 'kununu', 'id_item'], $cacheItemStub->getTags()); + // Since the properties are protected we need to bind it to this closure to be able to set them. + $item = Closure::bind( + static function(string $key, mixed $value, bool $isHit): CacheItem { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + $item->isTaggable = true; + + return $item; + }, + null, + CacheItem::class + ); + + return $item($cacheItem->getKey(), $cacheItem->get(), $cacheItem->isHit()); + } + + private function assertTags(CacheItemInterface $cacheItem): void + { + self::assertInstanceOf(CacheItem::class, $cacheItem); + + // CacheItem::getMetadata returns the metadata (where the tags are stored). + // + // Since the values are only updated when the cache adapter commits, we need to get the "newMetadata" + // property. That is protected, so let's bypass protection rules and get the property value + $tags = (Closure::bind(fn(): array => array_keys($this->newMetadata['tags'] ?? []), $cacheItem, $cacheItem))(); + + self::assertEquals(['test', 'kununu', 'id_item'], $tags); } } diff --git a/tests/Serializer/Provider/AbstractCacheSerializerTestCase.php b/tests/Serializer/Provider/AbstractCacheSerializerTestCase.php index 9a2a944..12ab058 100644 --- a/tests/Serializer/Provider/AbstractCacheSerializerTestCase.php +++ b/tests/Serializer/Provider/AbstractCacheSerializerTestCase.php @@ -19,14 +19,14 @@ public function testSerialize(): void { $result = $this->serializer->serialize($this->item); - $this->assertEquals($this->serializedResult, $result); + self::assertEquals($this->serializedResult, $result); } public function testDeserialize(): void { $result = $this->serializer->deserialize($this->serializedResult, ProjectionItemIterableStub::class); - $this->assertEquals($result, $this->item); + self::assertEquals($result, $this->item); } abstract protected function getSerializer(): CacheSerializerInterface; diff --git a/tests/Serializer/Provider/AbstractSymfonySerializerTestCase.php b/tests/Serializer/Provider/AbstractSymfonySerializerTestCase.php new file mode 100644 index 0000000..48bc173 --- /dev/null +++ b/tests/Serializer/Provider/AbstractSymfonySerializerTestCase.php @@ -0,0 +1,22 @@ +getSymfonySerializer()); } } diff --git a/tests/Serializer/Provider/SymfonyDeflatedCacheSerializerTest.php b/tests/Serializer/Provider/SymfonyDeflatedCacheSerializerTest.php index a25efbe..b280362 100644 --- a/tests/Serializer/Provider/SymfonyDeflatedCacheSerializerTest.php +++ b/tests/Serializer/Provider/SymfonyDeflatedCacheSerializerTest.php @@ -5,23 +5,13 @@ use Kununu\Projections\Serializer\CacheSerializerInterface; use Kununu\Projections\Serializer\Provider\SymfonyDeflatedCacheSerializer; -use Symfony\Component\Serializer\Encoder\JsonEncoder; -use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; -use Symfony\Component\Serializer\Serializer; -final class SymfonyDeflatedCacheSerializerTest extends AbstractCacheSerializerTestCase +final class SymfonyDeflatedCacheSerializerTest extends AbstractSymfonySerializerTestCase { protected const RESULT_FILE = 'symfony-deflate.result'; protected function getSerializer(): CacheSerializerInterface { - return new SymfonyDeflatedCacheSerializer( - new Serializer( - [new ArrayDenormalizer(), new PropertyNormalizer(), new ObjectNormalizer()], - [new JsonEncoder()] - ) - ); + return new SymfonyDeflatedCacheSerializer($this->getSymfonySerializer()); } } diff --git a/tests/Serializer/Provider/resources/igbinary-deflate.result b/tests/Serializer/Provider/resources/igbinary-deflate.result index 73b1d8364d18341716ea3dd9b8085ea4102ab53c..e2aeb805788af0ecd5cefed47d1d3dbf337c432c 100644 GIT binary patch delta 96 zcmV-m0H6Pq0gnNYHd!)drU-Hx8X23InweWz3bGcLl%}N#vinvl_$B713i>fn%PxkL z#F9i2MnTrpijty4pv~-wDJey%#l?ag;Tee~sb#4}f-J?wnJFBbmI<;Y0?jB(Wd#7@ Cz$w50 delta 101 zcmV-r0Gj`g0h9rdHeUKLP|Gle%oIURLnC7oQ!{f5OF@6?8C+aanwBQW?pvwgmzbL> z=tn)1QxZ!OMHmHHQ!7e}5`m$?o|uwSlv-RY$Pu2ASdvz~V diff --git a/tests/Serializer/Provider/resources/igbinary.result b/tests/Serializer/Provider/resources/igbinary.result index 203d4ca4f6e72793ab05824e5802050b89a5669b..87d4306f68b6495646abb1dbc52ceeeb914dff01 100644 GIT binary patch delta 26 hcmZo>e$O}|P=F~jMUd0b$k@cx%-q6KkagnMJ^)`d2Uq|A delta 33 ncmaFQ*vvd3kkMyip%;H0}jXO$v+_h!9{u47o3?_=0= z4ya>pK*>f52`>P+mpI{C5YuYTjH`-$?LsUH+h0TX;P%8#vulCeOD;zXsH z&Wn&!1omUogk2--UhBQ-Xh}K3>R%cwYrKKdU=(ZwN>A^WV$l<_vDOiDAPe|%GoB`L bAu3ViVBexjb)uxo^?wnOKpJevN_SKIWRlxG$z<;f0}{P-Ko?VogOsCuwJ1&r`5s8WR0QkknpLm`+W z1Yr~>smgM%RlX!c>`>QpEW+-!-c!r7%9ey^vIGTT;5$kX_-V~-d`m+htqqHKvIAdk fWO-!cWsD%=i)b)TR1`bQbxe*kx<@MvifNiY!jV-x diff --git a/tests/Serializer/Provider/resources/php.result b/tests/Serializer/Provider/resources/php.result index 645316045636efcf7593c40a3990218b4013faf3..202579574db0f3b80c142be54c9b5c61ea1d9b74 100644 GIT binary patch delta 39 scmX@Zyp3r>sEm=7Qf7*hb+MJDm6D;6v5BdfxrHT&Z)&A9nVs<{0NLmYTL1t6 delta 43 vcmdnSbcT6CD3iI_#4aC+%oHW-Vk=84B|{@)6H_yD3ri5++!81^vG6DW92N~s diff --git a/tests/Stubs/CacheItem/CacheItemStub.php b/tests/Stubs/CacheItem/CacheItemStub.php index eeff327..7f9a27f 100644 --- a/tests/Stubs/CacheItem/CacheItemStub.php +++ b/tests/Stubs/CacheItem/CacheItemStub.php @@ -13,7 +13,7 @@ final class CacheItemStub implements CacheItemInterface private bool $isHit = false; private array $tags = []; - public function __construct(private string $key) + public function __construct(private readonly string $key) { } @@ -32,38 +32,38 @@ public function isHit(): bool return $this->isHit; } - public function set(mixed $value): CacheItemStub + public function set(mixed $value): static { $this->value = $value; return $this; } - public function setHit(): CacheItemInterface + public function setHit(): CacheItemStub { $this->isHit = true; return $this; } - public function setNotHit(): CacheItemInterface + public function setNotHit(): CacheItemStub { $this->isHit = false; return $this; } - public function expiresAt(?DateTimeInterface $expiration): CacheItemInterface + public function expiresAt(?DateTimeInterface $expiration): static { return $this; } - public function expiresAfter(DateInterval|int|null $time): CacheItemInterface + public function expiresAfter(DateInterval|int|null $time): static { return $this; } - public function tag(array $tags): CacheItemInterface + public function tag(array $tags): CacheItemStub { $this->tags = $tags; diff --git a/tests/Stubs/ProjectionItem/ProjectionItemIterableStub.php b/tests/Stubs/ProjectionItem/ProjectionItemIterableStub.php index 98ec155..4b20e33 100644 --- a/tests/Stubs/ProjectionItem/ProjectionItemIterableStub.php +++ b/tests/Stubs/ProjectionItem/ProjectionItemIterableStub.php @@ -15,20 +15,10 @@ final class ProjectionItemIterableStub implements ProjectionItemIterableInterfac private const PROJECTION_KEY = 'test_iterable_%s'; - public function __construct(private string $id, private ?string $stuff = null) + public function __construct(public readonly string $id, public readonly ?string $stuff = null) { } - public function id(): string - { - return $this->id; - } - - public function stuff(): string - { - return $this->stuff; - } - public function getKey(): string { return sprintf(self::PROJECTION_KEY, $this->id); diff --git a/tests/Stubs/ProjectionItem/ProjectionItemStub.php b/tests/Stubs/ProjectionItem/ProjectionItemStub.php index 6aa1103..e373763 100644 --- a/tests/Stubs/ProjectionItem/ProjectionItemStub.php +++ b/tests/Stubs/ProjectionItem/ProjectionItemStub.php @@ -11,7 +11,7 @@ final class ProjectionItemStub implements ProjectionItemInterface { private const PROJECTION_KEY = 'test_%s'; - public function __construct(private string $id) + public function __construct(private readonly string $id) { } diff --git a/tests/Stubs/Provider/CachedProviderStub.php b/tests/Stubs/Provider/CachedProviderStub.php index 60aeaaa..3a52c7f 100644 --- a/tests/Stubs/Provider/CachedProviderStub.php +++ b/tests/Stubs/Provider/CachedProviderStub.php @@ -12,7 +12,7 @@ final class CachedProviderStub extends AbstractCachedProvider implements ProviderStubInterface { public function __construct( - private ProviderStubInterface $provider, + private readonly ProviderStubInterface $provider, ProjectionRepositoryInterface $projectionRepository, LoggerInterface $logger ) { diff --git a/tests/Tag/ProjectionTagGeneratorTest.php b/tests/Tag/ProjectionTagGeneratorTest.php index 01ad667..fe81836 100644 --- a/tests/Tag/ProjectionTagGeneratorTest.php +++ b/tests/Tag/ProjectionTagGeneratorTest.php @@ -15,7 +15,7 @@ public function testCreateTagsFromArray(): void { $tags = $this::createTagsFromArray('tag_1', 'tag_2'); - $this->assertInstanceOf(Tags::class, $tags); - $this->assertEquals(['tag_1', 'tag_2'], $tags->raw()); + self::assertInstanceOf(Tags::class, $tags); + self::assertEquals(['tag_1', 'tag_2'], $tags->raw()); } } diff --git a/tests/Tag/TagTest.php b/tests/Tag/TagTest.php index 8c36636..5c7379c 100644 --- a/tests/Tag/TagTest.php +++ b/tests/Tag/TagTest.php @@ -12,15 +12,15 @@ public function testThatTagCanBeCreated(): void { $tag = new Tag('tag'); - $this->assertInstanceOf(Tag::class, $tag); - $this->assertEquals('tag', $tag->value()); + self::assertInstanceOf(Tag::class, $tag); + self::assertEquals('tag', $tag->tag); } public function testThatTagCanBeTreatedAsString(): void { $tag = new Tag('tag'); - $this->assertEquals('tag', (string) $tag); + self::assertEquals('tag', (string) $tag); } public function testThatWhenTwoTagsHaveTheSameValueThenTheyAreEqual(): void @@ -28,6 +28,16 @@ public function testThatWhenTwoTagsHaveTheSameValueThenTheyAreEqual(): void $tag1 = new Tag('tag'); $tag2 = new Tag('tag'); - $this->assertTrue($tag2->equals($tag1)); + self::assertTrue($tag1->equals($tag2)); + self::assertTrue($tag2->equals($tag1)); + } + + public function testThatWhenTwoTagsHaveDistinctValuesThenTheyAreDifferent(): void + { + $tag1 = new Tag('tag_1'); + $tag2 = new Tag('tag_2'); + + self::assertFalse($tag1->equals($tag2)); + self::assertFalse($tag2->equals($tag1)); } } diff --git a/tests/Tag/TagsTest.php b/tests/Tag/TagsTest.php index e4ffa09..3519546 100644 --- a/tests/Tag/TagsTest.php +++ b/tests/Tag/TagsTest.php @@ -13,14 +13,14 @@ public function testThatCanBeCreatedWithoutTags(): void { $tags = new Tags(); - $this->assertCount(0, $tags->raw()); + self::assertEmpty($tags->raw()); } public function testThatCanBeCreatedWithTags(): void { $tags = new Tags(new Tag('tag_1'), new Tag('tag_2')); - $this->assertCount(2, $tags->raw()); + self::assertCount(2, $tags->raw()); } public function testUniquenessOfTags(): void @@ -33,14 +33,14 @@ public function testUniquenessOfTags(): void new Tag('tag_2') ); - $this->assertCount(3, $tags->raw()); + self::assertCount(3, $tags->raw()); } public function testThatRawReturnsAllTagsAsAnArray(): void { $tags = new Tags(new Tag('tag_1'), new Tag('tag_2'), new Tag('tag_3')); - $this->assertEquals( + self::assertEquals( ['tag_1', 'tag_2', 'tag_3'], $tags->raw() );