2026-08-20 17:35:38 +02:00
< ? php
declare ( strict_types = 1 );
namespace Tests\Unit\Domain\Services ;
2026-08-21 09:45:06 +02:00
use Domain\Events\BrandCreated ;
2026-08-21 11:36:37 +02:00
use Domain\Events\BrandRenamed ;
2026-08-20 17:35:38 +02:00
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 ;
2026-08-21 09:45:06 +02:00
use Shared\DDD\Event\DomainEventDispatcher ;
use Shared\DDD\Event\Event ;
2026-08-20 17:35:38 +02:00
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 ();
});
2026-08-21 09:45:06 +02:00
it ( 'returns existing brand if already present in repository without dispatching event' , function () use ( $arcadiaDirectory , $brandRepository , $codeGenerator ) : void {
2026-08-20 17:35:38 +02:00
// GIVEN
$existing = Brand :: hydrate (
id : new BrandId ( Uuid :: uuid7 ()),
name : new BrandName ( 'Le Gaulois' ),
code : new BrandCode ( 'LGL01' ),
2026-08-24 21:45:04 +02:00
arcadiaId : new ArcadiaId ( '1' ),
2026-08-20 17:35:38 +02:00
);
$brandRepository -> add ( $existing );
2026-08-21 09:45:06 +02:00
$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 );
2026-08-20 17:35:38 +02:00
// WHEN
2026-08-24 21:45:04 +02:00
$resolved = $resolver -> resolve ( new ArcadiaId ( '1' ));
2026-08-20 17:35:38 +02:00
// THEN
2026-08-21 09:45:06 +02:00
expect ( $resolved ) -> toBe ( $existing )
-> and ( $dispatcher -> dispatched ) -> toBeEmpty ();
2026-08-20 17:35:38 +02:00
});
2026-08-21 09:45:06 +02:00
it ( 'generates, persists and dispatches BrandCreated event when brand is not in repository but in Arcadia directory' , function () use ( $arcadiaDirectory , $brandRepository , $codeGenerator ) : void {
2026-08-20 17:35:38 +02:00
// GIVEN
2026-08-24 21:45:04 +02:00
$arcadiaDirectory -> add ( new ArcadiaBrand ( new ArcadiaId ( '2' ), new BrandName ( 'Maître CoQ' )));
2026-08-21 09:45:06 +02:00
$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 );
2026-08-20 17:35:38 +02:00
// WHEN
2026-08-24 21:45:04 +02:00
$resolved = $resolver -> resolve ( new ArcadiaId ( '2' ));
2026-08-20 17:35:38 +02:00
// THEN
2026-08-24 21:45:04 +02:00
expect ( $resolved -> arcadiaId -> value ) -> toBe ( '2' )
2026-08-20 17:35:38 +02:00
-> and ( $resolved -> name -> value ) -> toBe ( 'Maître CoQ' )
-> and ( str_starts_with ( $resolved -> code -> value , 'MCQ' )) -> toBeTrue ();
2026-08-24 21:45:04 +02:00
expect ( $brandRepository -> findByArcadiaId ( new ArcadiaId ( '2' ))) -> not -> toBeNull ();
2026-08-21 09:45:06 +02:00
expect ( $dispatcher -> dispatched ) -> toHaveCount ( 1 )
-> and ( $dispatcher -> dispatched [ 0 ]) -> toBeInstanceOf ( BrandCreated :: class );
/** @var BrandCreated $event */
$event = $dispatcher -> dispatched [ 0 ];
2026-08-24 21:45:04 +02:00
expect ( $event -> arcadiaId -> value ) -> toBe ( '2' )
2026-08-21 09:45:06 +02:00
-> and ( $event -> brandName -> value ) -> toBe ( 'Maître CoQ' )
-> and ( $event -> brandCode -> value ) -> toBe ( $resolved -> code -> value )
-> and ( $event -> brandId -> value ) -> toBe ( $resolved -> id -> value );
2026-08-20 17:35:38 +02:00
});
2026-08-21 09:45:06 +02:00
it ( 'throws BrandNotFoundException when resolving an unknown ArcadiaId' , function () use ( $arcadiaDirectory , $brandRepository , $codeGenerator ) : void {
2026-08-20 17:35:38 +02:00
// GIVEN
2026-08-21 09:45:06 +02:00
$dispatcher = new class ( ) implements DomainEventDispatcher
{
public function dispatch ( Event $event ) : void
{
}
};
$resolver = new BrandResolver ( $arcadiaDirectory , $brandRepository , $codeGenerator , $dispatcher );
2026-08-20 17:35:38 +02:00
// WHEN & THEN
2026-08-24 21:45:04 +02:00
expect ( fn () => $resolver -> resolve ( new ArcadiaId ( '999' )))
2026-08-20 17:35:38 +02:00
-> toThrow ( BrandNotFoundException :: class , 'Arcadia brand with ID 999 not found.' );
});
2026-08-21 11:36:37 +02:00
it ( 'updates brand name, persists and dispatches BrandRenamed when Arcadia brand name changes' , 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' ),
2026-08-24 21:45:04 +02:00
arcadiaId : new ArcadiaId ( '1' ),
2026-08-21 11:36:37 +02:00
);
$brandRepository -> add ( $oldBrand );
// Arcadia has renamed the brand
2026-08-24 21:45:04 +02:00
$arcadiaDirectory -> add ( new ArcadiaBrand ( new ArcadiaId ( '1' ), new BrandName ( 'Le Gaulois France' )));
2026-08-21 11:36:37 +02:00
$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
2026-08-24 21:45:04 +02:00
$resolved = $resolver -> resolve ( new ArcadiaId ( '1' ));
2026-08-21 11:36:37 +02:00
// THEN
expect ( $resolved -> name -> value ) -> toBe ( 'Le Gaulois France' )
-> and ( $resolved -> id () -> value ) -> toBe ( $brandId -> value )
-> and ( $resolved -> code -> value ) -> toBe ( 'LGL01' );
2026-08-24 21:45:04 +02:00
$persisted = $brandRepository -> findByArcadiaId ( new ArcadiaId ( '1' ));
2026-08-21 11:36:37 +02:00
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 );
});
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' ),
2026-08-24 21:45:04 +02:00
arcadiaId : new ArcadiaId ( '77' ),
2026-08-21 11:36:37 +02:00
);
$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
2026-08-24 21:45:04 +02:00
$resolved = $resolver -> resolve ( new ArcadiaId ( '77' ));
2026-08-21 11:36:37 +02:00
// THEN
expect ( $resolved ) -> toBe ( $existing )
-> and ( $resolved -> name -> value ) -> toBe ( 'Historical Brand' )
-> and ( $dispatcher -> dispatched ) -> toBeEmpty ();
});
2026-08-20 17:35:38 +02:00
});