toString(); $brandId = new BrandId(Uuid::fromString($uuid)); $cacheData = [ 'id' => $uuid, 'name' => 'Le Gaulois', 'code' => 'LGL01', 'arcadiaId' => '1', ]; /** @var CacheInterface&Mockery\MockInterface $cacheMock */ $cacheMock = Mockery::mock(CacheInterface::class); $cacheMock->shouldReceive('get') ->once() ->with("brand:id:{$uuid}") ->andReturn($cacheData); /** @var \Psr\Log\LoggerInterface&Mockery\MockInterface $loggerMock */ $loggerMock = Mockery::mock(\Psr\Log\LoggerInterface::class); $loggerMock->shouldReceive('warning')->zeroOrMoreTimes(); $brandCache = new BrandCache($cacheMock, $loggerMock); // WHEN $result = $brandCache->getById($brandId); // THEN assert($result !== null); expect($result)->not->toBeNull() ->and($result->id()->value)->toBe($brandId->value) ->and($result->name->value)->toBe('Le Gaulois') ->and($result->code->value)->toBe('LGL01') ->and($result->arcadiaId->value)->toBe('1'); }); it('returns brand from cache when found by code', function (): void { // GIVEN $uuid = Uuid::uuid7()->toString(); $brandCode = new BrandCode('LGL01'); $cacheData = [ 'id' => $uuid, 'name' => 'Le Gaulois', 'code' => 'LGL01', 'arcadiaId' => '1', ]; /** @var CacheInterface&Mockery\MockInterface $cacheMock */ $cacheMock = Mockery::mock(CacheInterface::class); $cacheMock->shouldReceive('get') ->once() ->with('brand:code:LGL01') ->andReturn($cacheData); /** @var \Psr\Log\LoggerInterface&Mockery\MockInterface $loggerMock */ $loggerMock = Mockery::mock(\Psr\Log\LoggerInterface::class); $loggerMock->shouldReceive('warning')->zeroOrMoreTimes(); $brandCache = new BrandCache($cacheMock, $loggerMock); // WHEN $result = $brandCache->getByCode($brandCode); // THEN assert($result !== null); expect($result)->not->toBeNull() ->and($result->code->value)->toBe('LGL01') ->and($result->name->value)->toBe('Le Gaulois'); }); it('returns brand from cache when found by arcadia id', function (): void { // GIVEN $uuid = Uuid::uuid7()->toString(); $arcadiaId = new ArcadiaId('10'); $cacheData = [ 'id' => $uuid, 'name' => 'Maître CoQ', 'code' => 'MCQ01', 'arcadiaId' => '10', ]; /** @var CacheInterface&Mockery\MockInterface $cacheMock */ $cacheMock = Mockery::mock(CacheInterface::class); $cacheMock->shouldReceive('get') ->once() ->with('brand:arcadia:10') ->andReturn($cacheData); /** @var \Psr\Log\LoggerInterface&Mockery\MockInterface $loggerMock */ $loggerMock = Mockery::mock(\Psr\Log\LoggerInterface::class); $loggerMock->shouldReceive('warning')->zeroOrMoreTimes(); $brandCache = new BrandCache($cacheMock, $loggerMock); // WHEN $result = $brandCache->getByArcadiaId($arcadiaId); // THEN assert($result !== null); expect($result)->not->toBeNull() ->and($result->arcadiaId->value)->toBe('10') ->and($result->name->value)->toBe('Maître CoQ'); }); it('returns null and recovers gracefully when cache data is corrupted', function (): void { // GIVEN $brandId = new BrandId(Uuid::uuid7()); /** @var CacheInterface&Mockery\MockInterface $cacheMock */ $cacheMock = Mockery::mock(CacheInterface::class); $cacheMock->shouldReceive('get') ->once() ->with("brand:id:{$brandId->value}") ->andReturn('corrupted_string'); /** @var \Psr\Log\LoggerInterface&Mockery\MockInterface $loggerMock */ $loggerMock = Mockery::mock(\Psr\Log\LoggerInterface::class); $loggerMock->shouldReceive('warning')->zeroOrMoreTimes(); $brandCache = new BrandCache($cacheMock, $loggerMock); // WHEN $result = $brandCache->getById($brandId); // THEN expect($result)->toBeNull(); }); it('stores brand in cache across all key variations', function (): void { // GIVEN $uuid = Uuid::uuid7()->toString(); $brand = Brand::hydrate( id: new BrandId(Uuid::fromString($uuid)), name: new BrandName('Doux'), code: new BrandCode('DOX01'), arcadiaId: new ArcadiaId('20'), ); $expectedData = [ 'id' => $uuid, 'name' => 'Doux', 'code' => 'DOX01', 'arcadiaId' => '20', ]; /** @var CacheInterface&Mockery\MockInterface $cacheMock */ $cacheMock = Mockery::mock(CacheInterface::class); $cacheMock->shouldReceive('set') ->once() ->with("brand:id:{$uuid}", $expectedData, 86400); $cacheMock->shouldReceive('set') ->once() ->with('brand:code:DOX01', $expectedData, 86400); $cacheMock->shouldReceive('set') ->once() ->with('brand:arcadia:20', $expectedData, 86400); /** @var \Psr\Log\LoggerInterface&Mockery\MockInterface $loggerMock */ $loggerMock = Mockery::mock(\Psr\Log\LoggerInterface::class); $loggerMock->shouldReceive('warning')->zeroOrMoreTimes(); $brandCache = new BrandCache($cacheMock, $loggerMock); // WHEN $brandCache->put($brand); // THEN expect(true)->toBeTrue(); }); it('invalidates specific brand keys and collection key', function (): void { // GIVEN $uuid = Uuid::uuid7()->toString(); $brand = Brand::hydrate( id: new BrandId(Uuid::fromString($uuid)), name: new BrandName('Père Dodu'), code: new BrandCode('PRD01'), arcadiaId: new ArcadiaId('30'), ); /** @var CacheInterface&Mockery\MockInterface $cacheMock */ $cacheMock = Mockery::mock(CacheInterface::class); $cacheMock->shouldReceive('delete') ->once() ->with('brand:all'); $cacheMock->shouldReceive('delete') ->once() ->with("brand:id:{$uuid}"); $cacheMock->shouldReceive('delete') ->once() ->with('brand:code:PRD01'); $cacheMock->shouldReceive('delete') ->once() ->with('brand:arcadia:30'); /** @var \Psr\Log\LoggerInterface&Mockery\MockInterface $loggerMock */ $loggerMock = Mockery::mock(\Psr\Log\LoggerInterface::class); $loggerMock->shouldReceive('warning')->zeroOrMoreTimes(); $brandCache = new BrandCache($cacheMock, $loggerMock); // WHEN $brandCache->invalidate($brand); // THEN expect(true)->toBeTrue(); }); it('resets all brand caches when reset is called', function (): void { // GIVEN $uuid = Uuid::uuid7()->toString(); $cacheData = [ [ 'id' => $uuid, 'name' => 'Brand 1', 'code' => 'BRD01', 'arcadiaId' => '1', ], ]; /** @var CacheInterface&Mockery\MockInterface $cacheMock */ $cacheMock = Mockery::mock(CacheInterface::class); $cacheMock->shouldReceive('get') ->once() ->with('brand:all') ->andReturn($cacheData); $cacheMock->shouldReceive('delete') ->once() ->with('brand:all'); $cacheMock->shouldReceive('delete') ->once() ->with("brand:id:{$uuid}"); $cacheMock->shouldReceive('delete') ->once() ->with('brand:code:BRD01'); $cacheMock->shouldReceive('delete') ->once() ->with('brand:arcadia:1'); /** @var \Psr\Log\LoggerInterface&Mockery\MockInterface $loggerMock */ $loggerMock = Mockery::mock(\Psr\Log\LoggerInterface::class); $loggerMock->shouldReceive('warning')->zeroOrMoreTimes(); $brandCache = new BrandCache($cacheMock, $loggerMock); // WHEN $brandCache->reset(); // THEN expect(true)->toBeTrue(); }); it('returns null and logs warning when getById throws exception', function (): void { // GIVEN $brandId = new BrandId(Uuid::uuid7()); /** @var CacheInterface&\Mockery\MockInterface $cacheMock */ $cacheMock = Mockery::mock(CacheInterface::class); $cacheMock->shouldReceive('get')->andThrow(new \RuntimeException('Redis error')); /** @var \Psr\Log\LoggerInterface&\Mockery\MockInterface $loggerMock */ $loggerMock = Mockery::mock(\Psr\Log\LoggerInterface::class); $loggerMock->shouldReceive('warning')->once(); $brandCache = new BrandCache($cacheMock, $loggerMock); // WHEN $result = $brandCache->getById($brandId); // THEN expect($result)->toBeNull(); }); it('logs warning and does not throw when put throws exception', function (): void { // GIVEN $brand = Brand::hydrate( id: new BrandId(Uuid::uuid7()), name: new BrandName('Ghost'), code: new BrandCode('GHO01'), arcadiaId: new ArcadiaId('999'), ); /** @var CacheInterface&\Mockery\MockInterface $cacheMock */ $cacheMock = Mockery::mock(CacheInterface::class); $cacheMock->shouldReceive('set')->andThrow(new \RuntimeException('Redis error')); /** @var \Psr\Log\LoggerInterface&\Mockery\MockInterface $loggerMock */ $loggerMock = Mockery::mock(\Psr\Log\LoggerInterface::class); $loggerMock->shouldReceive('warning')->once(); $brandCache = new BrandCache($cacheMock, $loggerMock); // WHEN $brandCache->put($brand); // THEN // No exception is thrown expect(true)->toBeTrue(); }); });