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 */ 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 */ 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('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'), arcadiaId: new ArcadiaId('1'), ); $brandRepository->add($oldBrand); // Arcadia has renamed the brand $arcadiaDirectory->add(new ArcadiaBrand(new ArcadiaId('1'), new BrandName('Le Gaulois France'))); $dispatcher = new class () implements DomainEventDispatcher { /** @var list */ 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->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); }); 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 */ 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(); }); });