From c85379b50e6b4cc08ccb5856ef7ca64042300d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Sun, 20 Sep 2026 17:17:33 +0200 Subject: [PATCH] refactor(application): align UpdateBrand on FindBrand polymorphic output pattern --- .../UseCases/Brand/Update/Input.php | 10 ++-- .../Brand/Update/Output/BrandNotFound.php | 9 ++++ .../{Output.php => Output/BrandUpdated.php} | 12 ++--- .../UseCases/Brand/Update/Output/Output.php | 11 +++++ .../UseCases/Brand/Update/UpdateBrand.php | 20 ++++---- .../UseCases/Brand/Update/UpdateBrandTest.php | 48 ++++++++++++------- 6 files changed, 73 insertions(+), 37 deletions(-) create mode 100644 src/Application/UseCases/Brand/Update/Output/BrandNotFound.php rename src/Application/UseCases/Brand/Update/{Output.php => Output/BrandUpdated.php} (59%) create mode 100644 src/Application/UseCases/Brand/Update/Output/Output.php diff --git a/src/Application/UseCases/Brand/Update/Input.php b/src/Application/UseCases/Brand/Update/Input.php index 45f86fe..5d59672 100644 --- a/src/Application/UseCases/Brand/Update/Input.php +++ b/src/Application/UseCases/Brand/Update/Input.php @@ -6,9 +6,9 @@ namespace Application\UseCases\Brand\Update; use Domain\Models\ValueObjects\Brand\BrandCode; use Shared\DDD\UseCases\Input as UseCaseInput; -use Shared\Models\ValueObjects\Email; -use Shared\Models\ValueObjects\PicturePath; -use Shared\Models\ValueObjects\Rcs; +use Shared\ValueObjects\Email; +use Domain\Models\ValueObjects\PicturePath; +use Shared\ValueObjects\Rcs; use Webmozart\Assert\Assert; final readonly class Input implements UseCaseInput @@ -21,8 +21,8 @@ final readonly class Input implements UseCaseInput ) { Assert::true( $this->logo instanceof PicturePath - || $this->rcs instanceof Rcs - || $this->contact instanceof Email, + || $this->rcs instanceof Rcs + || $this->contact instanceof Email, 'At least one field must be provided to update the brand.', ); } diff --git a/src/Application/UseCases/Brand/Update/Output/BrandNotFound.php b/src/Application/UseCases/Brand/Update/Output/BrandNotFound.php new file mode 100644 index 0000000..f3eb329 --- /dev/null +++ b/src/Application/UseCases/Brand/Update/Output/BrandNotFound.php @@ -0,0 +1,9 @@ + */ -final readonly class UpdateBrand implements UseCase +/** @implements UseCase<\Application\UseCases\Brand\Update\Input, \Application\UseCases\Brand\Update\Output\Output> */ +final readonly class UpdateBrand implements UseCase, Transactional { public function __construct( private BrandRepository $repository, @@ -30,7 +33,7 @@ final readonly class UpdateBrand implements UseCase $brand = $this->repository->findByCode($input->code); if (! $brand instanceof Brand) { - throw new NotFoundException(); + return new BrandNotFound(); } if ($input->logo instanceof PicturePath) { @@ -47,13 +50,14 @@ final readonly class UpdateBrand implements UseCase $this->repository->save($brand); - return new Output( + return new BrandUpdated( name: $brand->name, code: $brand->code, logo: $brand->logo, rcs: $brand->rcs, contact: $brand->contact, createdAt: $brand->createdAt(), + updatedAt: $brand->updatedAt(), ); } } diff --git a/tests/Unit/Application/UseCases/Brand/Update/UpdateBrandTest.php b/tests/Unit/Application/UseCases/Brand/Update/UpdateBrandTest.php index 7508ae6..e18077e 100644 --- a/tests/Unit/Application/UseCases/Brand/Update/UpdateBrandTest.php +++ b/tests/Unit/Application/UseCases/Brand/Update/UpdateBrandTest.php @@ -5,20 +5,21 @@ declare(strict_types=1); namespace Tests\Unit\Application\UseCases\Brand\Update; use Application\UseCases\Brand\Update\Input; -use Application\UseCases\Brand\Update\Output; +use Application\UseCases\Brand\Update\Output\BrandNotFound; +use Application\UseCases\Brand\Update\Output\BrandUpdated; +use Application\UseCases\Brand\Update\Output\Output; use Application\UseCases\Brand\Update\UpdateBrand; use Domain\Models\Entities\Brand; use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Brand\BrandId; use Domain\Models\ValueObjects\Brand\BrandName; +use Domain\Models\ValueObjects\PicturePath; use Domain\Ports\Repositories\BrandRepository; -use Shared\DDD\Exceptions\NotFoundException; use Shared\DDD\UseCases\Input as BaseInput; -use Shared\Models\ValueObjects\Email; -use Shared\Models\ValueObjects\PicturePath; -use Shared\Models\ValueObjects\Rcs; +use Shared\ValueObjects\Email; +use Shared\ValueObjects\Rcs; -covers(UpdateBrand::class); +covers(UpdateBrand::class, Input::class, BrandUpdated::class, BrandNotFound::class); $existingBrand = static fn (BrandCode $code): Brand => Brand::hydrate( BrandId::generate(), @@ -46,7 +47,8 @@ describe('update brand use case', function () use ($existingBrand): void { && $saved->rcs === $newRcs && $saved->contact === $newContact && $saved->code === $brand->code - && $saved->name === $brand->name, + && $saved->name === $brand->name + && $saved->updatedAt !== null, )); $useCase = new UpdateBrand($repository); @@ -55,8 +57,9 @@ describe('update brand use case', function () use ($existingBrand): void { $result = $useCase->execute($input); // THEN - expect($result)->toBeInstanceOf(Output::class) - ->and($result->code)->toBe($brand->code); + expect($result)->toBeInstanceOf(BrandUpdated::class) + ->and($result->code)->toBe($brand->code) + ->and($result->updatedAt)->not->toBeNull(); }); it('updates only the contact and leaves logo/rcs untouched', function () use ($existingBrand): void { @@ -73,19 +76,24 @@ describe('update brand use case', function () use ($existingBrand): void { && $saved->logo === $brand->logo && $saved->rcs === $brand->rcs && $saved->code === $brand->code - && $saved->name === $brand->name, + && $saved->name === $brand->name + && $saved->updatedAt !== null, )); $useCase = new UpdateBrand($repository); // WHEN - $useCase->execute($input); + $result = $useCase->execute($input); - // THEN handled via the save() expectation above - expect(true)->toBeTrue(); + // THEN + expect($result)->toBeInstanceOf(BrandUpdated::class) + ->and($result->code)->toBe($brand->code) + ->and($result->updatedAt)->not->toBeNull(); }); +}); - it('throws when the brand does not exist', function (): void { +describe('update brand use case validation rules', function (): void { + it('returns BrandNotFound when the brand does not exist', function (): void { // GIVEN $code = new BrandCode('AAA01'); $input = new Input(code: $code, contact: new Email('contact-new@example.com')); @@ -96,11 +104,15 @@ describe('update brand use case', function () use ($existingBrand): void { $useCase = new UpdateBrand($repository); - // WHEN / THEN - expect(fn (): Output => $useCase->execute($input)) - ->toThrow(NotFoundException::class); - }); + // WHEN + $result = $useCase->execute($input); + // THEN + expect($result)->toBeInstanceOf(BrandNotFound::class); + }); +}); + +describe('update brand use case assertions', function (): void { it('rejects an input of the wrong type', function (): void { // GIVEN $repository = \Mockery::mock(BrandRepository::class);