ldc-arcadia-api/tests/Unit/Infra/Cache/BrandCacheTest.php

313 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\Infra\Cache;
use Domain\Models\Entities\Brand;
use Domain\Models\ValueObjects\Brand\ArcadiaId;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Brand\BrandId;
use Domain\Models\ValueObjects\Brand\BrandName;
use Infra\Cache\BrandCache;
use Mockery;
use Psr\SimpleCache\CacheInterface;
use Ramsey\Uuid\Uuid;
covers(BrandCache::class);
describe('BrandCache Unit Tests', function (): void {
it('returns brand from cache when found by id', function (): void {
// GIVEN
$uuid = Uuid::uuid7()->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();
});
});