240 lines
8.9 KiB
PHP
240 lines
8.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Domain\Services;
|
|
|
|
use Domain\Events\BrandCreated;
|
|
use Domain\Exceptions\BrandNotFoundException;
|
|
use Domain\Models\Entities\Brand;
|
|
use Domain\Models\ValueObjects\Brand\ArcadiaBrand;
|
|
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 Domain\Services\BrandCodeGenerator;
|
|
use Domain\Services\BrandResolver;
|
|
use Infra\Adapters\Directories\InMemoryArcadiaBrandDirectory;
|
|
use Infra\Adapters\Repositories\InMemoryBrandRepository;
|
|
use Ramsey\Uuid\Uuid;
|
|
use Shared\DDD\Event\DomainEventDispatcher;
|
|
use Shared\DDD\Event\Event;
|
|
use Domain\Events\BrandRenamed;
|
|
|
|
covers(BrandResolver::class);
|
|
|
|
describe('BrandResolver Unit Tests', function (): void {
|
|
$arcadiaDirectory = new InMemoryArcadiaBrandDirectory();
|
|
$brandRepository = new InMemoryBrandRepository();
|
|
$codeGenerator = new BrandCodeGenerator();
|
|
|
|
beforeEach(function () use ($arcadiaDirectory, $brandRepository): void {
|
|
$arcadiaDirectory->clear();
|
|
$brandRepository->clear();
|
|
});
|
|
|
|
it('returns existing brand if already present in repository without dispatching event', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator): void {
|
|
// GIVEN
|
|
$existing = Brand::hydrate(
|
|
id: new BrandId(Uuid::uuid7()),
|
|
name: new BrandName('Le Gaulois'),
|
|
code: new BrandCode('LGL01'),
|
|
arcadiaId: new ArcadiaId('1'),
|
|
);
|
|
$brandRepository->add($existing);
|
|
|
|
$dispatcher = new class () implements DomainEventDispatcher
|
|
{
|
|
/** @var list<Event> */
|
|
public array $dispatched = [];
|
|
|
|
public function dispatch(Event $event): void
|
|
{
|
|
$this->dispatched[] = $event;
|
|
}
|
|
};
|
|
|
|
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $dispatcher);
|
|
|
|
// WHEN
|
|
$resolved = $resolver->resolve(new ArcadiaId('1'));
|
|
|
|
// THEN
|
|
expect($resolved)->toBe($existing)
|
|
->and($dispatcher->dispatched)->toBeEmpty();
|
|
});
|
|
|
|
it('generates, persists and dispatches BrandCreated event when brand is not in repository but in Arcadia directory', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator): void {
|
|
// GIVEN
|
|
$arcadiaDirectory->add(new ArcadiaBrand(new ArcadiaId('2'), new BrandName('Maître CoQ')));
|
|
|
|
$dispatcher = new class () implements DomainEventDispatcher
|
|
{
|
|
/** @var list<Event> */
|
|
public array $dispatched = [];
|
|
|
|
public function dispatch(Event $event): void
|
|
{
|
|
$this->dispatched[] = $event;
|
|
}
|
|
};
|
|
|
|
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $dispatcher);
|
|
|
|
// WHEN
|
|
$resolved = $resolver->resolve(new ArcadiaId('2'));
|
|
|
|
// THEN
|
|
expect($resolved->arcadiaId->value)->toBe('2')
|
|
->and($resolved->name->value)->toBe('Maître Coq')
|
|
->and(str_starts_with($resolved->code->value, 'MCQ'))->toBeTrue();
|
|
|
|
expect($brandRepository->findByArcadiaId(new ArcadiaId('2')))->not->toBeNull();
|
|
|
|
expect($dispatcher->dispatched)->toHaveCount(1)
|
|
->and($dispatcher->dispatched[0])->toBeInstanceOf(BrandCreated::class);
|
|
|
|
/** @var BrandCreated $event */
|
|
$event = $dispatcher->dispatched[0];
|
|
expect($event->arcadiaId->value)->toBe('2')
|
|
->and($event->brandName->value)->toBe('Maître Coq')
|
|
->and($event->brandCode->value)->toBe($resolved->code->value)
|
|
->and($event->brandId->value)->toBe($resolved->id->value);
|
|
});
|
|
|
|
it('throws BrandNotFoundException when resolving an unknown ArcadiaId', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator): void {
|
|
// GIVEN
|
|
$dispatcher = new class () implements DomainEventDispatcher
|
|
{
|
|
public function dispatch(Event $event): void
|
|
{
|
|
}
|
|
};
|
|
|
|
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $dispatcher);
|
|
|
|
// WHEN & THEN
|
|
expect(fn () => $resolver->resolve(new ArcadiaId('999')))
|
|
->toThrow(BrandNotFoundException::class, 'Arcadia brand with ID 999 not found.');
|
|
});
|
|
|
|
it('returns existing brand directly without querying Arcadia directory when already in repository', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator): void {
|
|
// GIVEN
|
|
$brandId = new BrandId(Uuid::uuid7());
|
|
$oldBrand = Brand::hydrate(
|
|
id: $brandId,
|
|
name: new BrandName('Le Gaulois'),
|
|
code: new BrandCode('LGL01'),
|
|
arcadiaId: new ArcadiaId('1'),
|
|
);
|
|
$brandRepository->add($oldBrand);
|
|
|
|
// Arcadia directory has a different name, but resolve() prioritizes local repository without extra Oracle calls
|
|
$arcadiaDirectory->add(new ArcadiaBrand(new ArcadiaId('1'), new BrandName('Le Gaulois France')));
|
|
|
|
$dispatcher = new class () implements DomainEventDispatcher
|
|
{
|
|
/** @var list<Event> */
|
|
public array $dispatched = [];
|
|
|
|
public function dispatch(Event $event): void
|
|
{
|
|
$this->dispatched[] = $event;
|
|
}
|
|
};
|
|
|
|
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $dispatcher);
|
|
|
|
// WHEN
|
|
$resolved = $resolver->resolve(new ArcadiaId('1'));
|
|
|
|
// THEN
|
|
expect($resolved)->toBe($oldBrand)
|
|
->and($resolved->name->value)->toBe('Le Gaulois')
|
|
->and($dispatcher->dispatched)->toBeEmpty();
|
|
});
|
|
|
|
it('preserves and returns existing brand when brand was removed from Arcadia directory', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator): void {
|
|
// GIVEN
|
|
$brandId = new BrandId(Uuid::uuid7());
|
|
$existing = Brand::hydrate(
|
|
id: $brandId,
|
|
name: new BrandName('Historical Brand'),
|
|
code: new BrandCode('HST01'),
|
|
arcadiaId: new ArcadiaId('77'),
|
|
);
|
|
$brandRepository->add($existing);
|
|
|
|
// Arcadia directory does NOT have brand 77 (deleted/purged amont)
|
|
// $arcadiaDirectory is empty for 77
|
|
|
|
$dispatcher = new class () implements DomainEventDispatcher
|
|
{
|
|
/** @var list<Event> */
|
|
public array $dispatched = [];
|
|
|
|
public function dispatch(Event $event): void
|
|
{
|
|
$this->dispatched[] = $event;
|
|
}
|
|
};
|
|
|
|
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $dispatcher);
|
|
|
|
// WHEN
|
|
$resolved = $resolver->resolve(new ArcadiaId('77'));
|
|
|
|
// THEN
|
|
expect($resolved)->toBe($existing)
|
|
->and($resolved->name->value)->toBe('Historical Brand')
|
|
->and($dispatcher->dispatched)->toBeEmpty();
|
|
});
|
|
|
|
it('updates brand name, persists and dispatches BrandRenamed when resolving from ArcadiaBrand with changed name', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator): void {
|
|
// GIVEN
|
|
$brandId = new BrandId(Uuid::uuid7());
|
|
$oldBrand = Brand::hydrate(
|
|
id: $brandId,
|
|
name: new BrandName('Le Gaulois'),
|
|
code: new BrandCode('LGL01'),
|
|
arcadiaId: new ArcadiaId('1'),
|
|
);
|
|
$brandRepository->add($oldBrand);
|
|
|
|
$renamedArcadia = new ArcadiaBrand(new ArcadiaId('1'), new BrandName('Le Gaulois France'));
|
|
|
|
$dispatcher = new class () implements DomainEventDispatcher
|
|
{
|
|
/** @var list<Event> */
|
|
public array $dispatched = [];
|
|
|
|
public function dispatch(Event $event): void
|
|
{
|
|
$this->dispatched[] = $event;
|
|
}
|
|
};
|
|
|
|
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $dispatcher);
|
|
|
|
// WHEN
|
|
$resolved = $resolver->resolveFromArcadia($renamedArcadia);
|
|
|
|
// THEN
|
|
expect($resolved->name->value)->toBe('Le Gaulois France')
|
|
->and($resolved->id()->value)->toBe($brandId->value)
|
|
->and($resolved->code->value)->toBe('LGL01');
|
|
|
|
$persisted = $brandRepository->findByArcadiaId(new ArcadiaId('1'));
|
|
expect($persisted)->not->toBeNull()
|
|
->and($persisted->name->value)->toBe('Le Gaulois France');
|
|
|
|
expect($dispatcher->dispatched)->toHaveCount(1)
|
|
->and($dispatcher->dispatched[0])->toBeInstanceOf(BrandRenamed::class);
|
|
|
|
/** @var \Domain\Events\BrandRenamed $event */
|
|
$event = $dispatcher->dispatched[0];
|
|
expect($event->oldName->value)->toBe('Le Gaulois')
|
|
->and($event->newName->value)->toBe('Le Gaulois France')
|
|
->and($event->brandId->value)->toBe($brandId->value);
|
|
});
|
|
});
|