From 8f933db9fa2e6f4143f773494facd65f268644f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 20 Aug 2026 18:16:31 +0200 Subject: [PATCH] feat(inco): migrate to IncoBrand VO, BrandResolver domain service and BrandCode filtering --- database/factories/IncoSheetFactory.php | 18 +- .../Api/V1/IncoSheets/List Inco Sheets.yml | 2 +- .../Api/V1/IncoSheets/Search Inco Sheets.yml | 2 +- documentation/OpenApi/openapi.yaml | 8 +- .../Usecase/Brands/FindAll/FindAllBrands.php | 14 +- .../Usecase/IncoSheets/Find/Output.php | 4 +- .../IncoSheets/FindAll/PaginatedIncoSheet.php | 4 +- .../Usecase/IncoSheets/Search/Input.php | 10 +- .../IncoSheets/Search/PaginatedIncoSheet.php | 4 +- src/Domain/Models/Entities/Inco/IncoSheet.php | 6 +- src/Domain/Models/ValueObjects/Inco/Brand.php | 21 -- .../Models/ValueObjects/Inco/IncoBrand.php | 19 ++ .../ValueObjects/Inco/IncoSheetFilter.php | 10 +- .../Ports/Directories/IncoSheetDirectory.php | 3 +- .../Directories/IncoShortSheetDirectory.php | 3 +- src/Domain/Services/BrandResolver.php | 54 +--- .../EloquentIncoSheetDirectory.php | 243 +++++++++++------- .../EloquentIncoShortSheetDirectory.php | 44 +++- .../InMemoryIncoSheetDirectory.php | 29 ++- .../InMemoryIncoShortSheetDirectory.php | 3 +- .../Repositories/EloquentBrandRepository.php | 112 +++++++- .../Api/V1/Dto/IncoSheets/Parts/BrandDto.php | 8 +- .../IncoSheets/IncoSheetsIndexRequest.php | 15 +- .../IncoSheets/IncoSheetsSearchRequest.php | 10 +- .../Api/V1/IncoSheets/IncoSheetsIndexTest.php | 39 +++ .../V1/IncoSheets/IncoSheetsSearchTest.php | 34 ++- .../Brands/FindAll/FindAllBrandsTest.php | 6 +- .../Incosheets/FindAllIncoSheetsDtoTest.php | 7 +- .../Incosheets/FindAllIncoSheetsTest.php | 7 +- .../Incosheets/FindIncoSheetDtoTest.php | 7 +- .../Usecase/Incosheets/FindIncoSheetTest.php | 10 +- .../Incosheets/SearchIncoSheetsTest.php | 9 +- .../Domain/Entities/IncoSheetEntityTest.php | 11 +- .../Models/ValueObjects/Inco/BrandTest.php | 38 --- .../ValueObjects/Inco/IncoBrandTest.php | 29 +++ .../ValueObjects/Inco/IncoSheetFilterTest.php | 22 +- .../Domain/Services/BrandResolverTest.php | 49 +--- .../InMemoryIncoSheetDirectoryTest.php | 27 +- 38 files changed, 589 insertions(+), 352 deletions(-) delete mode 100644 src/Domain/Models/ValueObjects/Inco/Brand.php create mode 100644 src/Domain/Models/ValueObjects/Inco/IncoBrand.php delete mode 100644 tests/Unit/Domain/Models/ValueObjects/Inco/BrandTest.php create mode 100644 tests/Unit/Domain/Models/ValueObjects/Inco/IncoBrandTest.php diff --git a/database/factories/IncoSheetFactory.php b/database/factories/IncoSheetFactory.php index 08dc140..4bb2169 100644 --- a/database/factories/IncoSheetFactory.php +++ b/database/factories/IncoSheetFactory.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; +use Infra\Database\Models\ArcadiaBrand; use Infra\Database\Models\IncoSheet; /** @@ -47,7 +48,7 @@ final class IncoSheetFactory extends Factory ['code' => 1, 'label' => 'Gluten'], ], 'brand' => [ - 'code' => 'M01', + 'code' => 1, 'label' => 'Le Gaulois', ], 'is_seasonal' => false, @@ -68,4 +69,19 @@ final class IncoSheetFactory extends Factory 'compo_id' => (string) fake()->numberBetween(10000, 99999), ]; } + + #[\Override] + public function configure(): static + { + return $this->afterCreating(function (IncoSheet $sheet): void { + $raw = is_array($sheet->brand) ? $sheet->brand : []; + $code = isset($raw['code']) && is_numeric($raw['code']) ? (int) $raw['code'] : 1; + $label = isset($raw['label']) && is_string($raw['label']) ? $raw['label'] : (string) ($raw['libelle'] ?? 'Le Gaulois'); + + ArcadiaBrand::query()->firstOrCreate( + ['code' => $code], + ['libelle' => $label] + ); + }); + } } diff --git a/documentation/Bruno/Arcadia Api/Api/V1/IncoSheets/List Inco Sheets.yml b/documentation/Bruno/Arcadia Api/Api/V1/IncoSheets/List Inco Sheets.yml index e9d7f68..c1757ce 100644 --- a/documentation/Bruno/Arcadia Api/Api/V1/IncoSheets/List Inco Sheets.yml +++ b/documentation/Bruno/Arcadia Api/Api/V1/IncoSheets/List Inco Sheets.yml @@ -31,7 +31,7 @@ http: description: Filter INCO sheets updated since timestamp (RFC3339) disabled: true - name: brands[] - value: M01 + value: LGL01 type: query description: Filter by brand code disabled: true diff --git a/documentation/Bruno/Arcadia Api/Api/V1/IncoSheets/Search Inco Sheets.yml b/documentation/Bruno/Arcadia Api/Api/V1/IncoSheets/Search Inco Sheets.yml index ed47dd0..5757312 100644 --- a/documentation/Bruno/Arcadia Api/Api/V1/IncoSheets/Search Inco Sheets.yml +++ b/documentation/Bruno/Arcadia Api/Api/V1/IncoSheets/Search Inco Sheets.yml @@ -12,7 +12,7 @@ http: type: query description: Pattern used with SQL-like matching rules - name: brands[] - value: M01 + value: LGL01 type: query description: Filter by brand code disabled: true diff --git a/documentation/OpenApi/openapi.yaml b/documentation/OpenApi/openapi.yaml index 6dc7835..5bed008 100644 --- a/documentation/OpenApi/openapi.yaml +++ b/documentation/OpenApi/openapi.yaml @@ -529,7 +529,8 @@ components: minItems: 1 items: type: string - example: ["M01", "M02"] + pattern: '^[A-Z]{3}\d{2}$' + example: ["LGL01", "MCQ01"] ShortProjection: name: short @@ -859,10 +860,11 @@ components: properties: code: type: string - example: M01 + pattern: '^[A-Z]{3}\d{2}$' + example: "LGL01" label: type: string - example: Le Gaulois + example: "Le Gaulois" Inco: type: object diff --git a/src/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrands.php b/src/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrands.php index 0d49fcf..ad1ab63 100644 --- a/src/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrands.php +++ b/src/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrands.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Application\Arcadia\Usecase\Brands\FindAll; -use Domain\Models\Entities\Brand; +use Domain\Ports\Directories\ArcadiaBrandDirectory; use Domain\Services\BrandResolver; use Shared\DDD\Usecase\Input as BaseInput; use Shared\DDD\Usecase\Usecase; @@ -14,6 +14,7 @@ use Webmozart\Assert\Assert; final readonly class FindAllBrands implements Usecase { public function __construct( + private ArcadiaBrandDirectory $arcadiaBrandDirectory, private BrandResolver $brandResolver, ) { } @@ -23,12 +24,13 @@ final readonly class FindAllBrands implements Usecase { Assert::isInstanceOf($input, Input::class); - $brands = $this->brandResolver->resolveAll(); + $arcadiaBrands = $this->arcadiaBrandDirectory->findAll(); + $brandItems = []; - $brandItems = array_map( - static fn (Brand $brand): BrandItem => BrandItem::fromDomain($brand), - $brands - ); + foreach ($arcadiaBrands as $arcadiaBrand) { + $brand = $this->brandResolver->resolve($arcadiaBrand->id); + $brandItems[] = BrandItem::fromDomain($brand); + } $total = count($brandItems); $offset = $input->pagination->from(); diff --git a/src/Application/Arcadia/Usecase/IncoSheets/Find/Output.php b/src/Application/Arcadia/Usecase/IncoSheets/Find/Output.php index 9b8f032..6612b00 100644 --- a/src/Application/Arcadia/Usecase/IncoSheets/Find/Output.php +++ b/src/Application/Arcadia/Usecase/IncoSheets/Find/Output.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Application\Arcadia\Usecase\IncoSheets\Find; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoWeight; use Domain\Models\ValueObjects\Inco\Ingredients; @@ -34,7 +34,7 @@ final readonly class Output implements UsecaseOutput public Ingredients $ingredients, public ?PortionAmount $portions, public array $allergens, - public Brand $brand, + public IncoBrand $brand, public bool $isSeasonal, public ?IncoWeight $weight, public ?Nutriscore $nutriscore, diff --git a/src/Application/Arcadia/Usecase/IncoSheets/FindAll/PaginatedIncoSheet.php b/src/Application/Arcadia/Usecase/IncoSheets/FindAll/PaginatedIncoSheet.php index 0ea5c84..8ebdada 100644 --- a/src/Application/Arcadia/Usecase/IncoSheets/FindAll/PaginatedIncoSheet.php +++ b/src/Application/Arcadia/Usecase/IncoSheets/FindAll/PaginatedIncoSheet.php @@ -6,8 +6,8 @@ namespace Application\Arcadia\Usecase\IncoSheets\FindAll; use Domain\Models\Entities\Inco\IncoSheet; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoWeight; use Domain\Models\ValueObjects\Inco\Ingredients; @@ -35,7 +35,7 @@ final readonly class PaginatedIncoSheet implements UsecaseOutput public Ingredients $ingredients, public ?PortionAmount $portions, public array $allergens, - public Brand $brand, + public IncoBrand $brand, public bool $isSeasonal, public ?IncoWeight $weight, public ?Nutriscore $nutriscore, diff --git a/src/Application/Arcadia/Usecase/IncoSheets/Search/Input.php b/src/Application/Arcadia/Usecase/IncoSheets/Search/Input.php index fee0728..4577d86 100644 --- a/src/Application/Arcadia/Usecase/IncoSheets/Search/Input.php +++ b/src/Application/Arcadia/Usecase/IncoSheets/Search/Input.php @@ -4,26 +4,22 @@ declare(strict_types=1); namespace Application\Arcadia\Usecase\IncoSheets\Search; +use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Inco\IncoIdQuery; use Shared\DDD\Usecase\Input as UsecaseInput; use Shared\Pagination; final readonly class Input implements UsecaseInput { - /** @var array */ + /** @var array */ public array $brands; public function __construct( public IncoIdQuery $query, public bool $short = false, public ?Pagination $pagination = null, - string ...$brands, + BrandCode ...$brands, ) { $this->brands = $brands; - foreach ($this->brands as $brand) { - if (trim($brand) === '') { - throw new \InvalidArgumentException('Every item in brands array must be a non empty string.'); - } - } } } diff --git a/src/Application/Arcadia/Usecase/IncoSheets/Search/PaginatedIncoSheet.php b/src/Application/Arcadia/Usecase/IncoSheets/Search/PaginatedIncoSheet.php index f35a877..7f8cb61 100644 --- a/src/Application/Arcadia/Usecase/IncoSheets/Search/PaginatedIncoSheet.php +++ b/src/Application/Arcadia/Usecase/IncoSheets/Search/PaginatedIncoSheet.php @@ -6,8 +6,8 @@ namespace Application\Arcadia\Usecase\IncoSheets\Search; use Domain\Models\Entities\Inco\IncoSheet; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoWeight; use Domain\Models\ValueObjects\Inco\Ingredients; @@ -35,7 +35,7 @@ final readonly class PaginatedIncoSheet implements UsecaseOutput public Ingredients $ingredients, public ?PortionAmount $portions, public array $allergens, - public Brand $brand, + public IncoBrand $brand, public bool $isSeasonal, public ?IncoWeight $weight, public ?Nutriscore $nutriscore, diff --git a/src/Domain/Models/Entities/Inco/IncoSheet.php b/src/Domain/Models/Entities/Inco/IncoSheet.php index a80a724..8ca091c 100644 --- a/src/Domain/Models/Entities/Inco/IncoSheet.php +++ b/src/Domain/Models/Entities/Inco/IncoSheet.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Domain\Models\Entities\Inco; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoWeight; use Domain\Models\ValueObjects\Inco\Ingredients; @@ -35,7 +35,7 @@ final readonly class IncoSheet implements Entity public Ingredients $ingredients, public ?PortionAmount $portions, public array $allergens, - public Brand $brand, + public IncoBrand $brand, public bool $isSeasonal, public ?IncoWeight $weight, public ?Nutriscore $nutriscore, @@ -70,7 +70,7 @@ final readonly class IncoSheet implements Entity Ingredients $ingredients, ?PortionAmount $portions, array $allergens, - Brand $brand, + IncoBrand $brand, bool $isSeasonal, ?IncoWeight $weight, ?Nutriscore $nutriscore, diff --git a/src/Domain/Models/ValueObjects/Inco/Brand.php b/src/Domain/Models/ValueObjects/Inco/Brand.php deleted file mode 100644 index cd75400..0000000 --- a/src/Domain/Models/ValueObjects/Inco/Brand.php +++ /dev/null @@ -1,21 +0,0 @@ - */ + /** @var array */ public array $brands; public function __construct( public ?\DateTimeInterface $since = null, - string ...$brands, + BrandCode ...$brands, ) { $this->brands = $brands; - if (array_any($this->brands, static fn (string $brand): bool => trim($brand) === '')) { - throw new \InvalidArgumentException('Every item in brands array must be a non empty string.'); - } - if (empty($this->brands) && $since === null) { throw new \DomainException('IncoSheetFilter requires at least one filtering criterion.'); } diff --git a/src/Domain/Ports/Directories/IncoSheetDirectory.php b/src/Domain/Ports/Directories/IncoSheetDirectory.php index 76d9de9..eb2f820 100644 --- a/src/Domain/Ports/Directories/IncoSheetDirectory.php +++ b/src/Domain/Ports/Directories/IncoSheetDirectory.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Domain\Ports\Directories; use Domain\Models\Entities\Inco\IncoSheet; +use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoIdQuery; use Domain\Models\ValueObjects\Inco\IncoSheetFilter; @@ -27,7 +28,7 @@ interface IncoSheetDirectory extends Directory public function search( IncoIdQuery $query, Pagination $pagination, - string ...$brands + BrandCode ...$brands ): Paginated; public function find(IncoId $id): ?IncoSheet; diff --git a/src/Domain/Ports/Directories/IncoShortSheetDirectory.php b/src/Domain/Ports/Directories/IncoShortSheetDirectory.php index e1231df..9688882 100644 --- a/src/Domain/Ports/Directories/IncoShortSheetDirectory.php +++ b/src/Domain/Ports/Directories/IncoShortSheetDirectory.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Domain\Ports\Directories; use Domain\Models\Entities\Inco\IncoShortSheet; +use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoIdQuery; use Domain\Models\ValueObjects\Inco\IncoSheetFilter; @@ -27,7 +28,7 @@ interface IncoShortSheetDirectory extends Directory public function search( IncoIdQuery $query, Pagination $pagination, - string ...$brands + BrandCode ...$brands ): Paginated; public function find(IncoId $id): ?IncoShortSheet; diff --git a/src/Domain/Services/BrandResolver.php b/src/Domain/Services/BrandResolver.php index bfd6b18..aaf25de 100644 --- a/src/Domain/Services/BrandResolver.php +++ b/src/Domain/Services/BrandResolver.php @@ -6,7 +6,6 @@ namespace Domain\Services; use Domain\Exceptions\BrandNotFoundException; use Domain\Models\Entities\Brand; -use Domain\Models\ValueObjects\Brand\ArcadiaBrand; use Domain\Models\ValueObjects\Brand\ArcadiaId; use Domain\Ports\Directories\ArcadiaBrandDirectory; use Domain\Ports\Repositories\BrandRepository; @@ -27,16 +26,24 @@ final readonly class BrandResolver } /** - * Resolve or generate/persist a Brand entity from an ArcadiaBrand. + * Resolve or generate/persist a Brand entity given an Arcadia brand code (ArcadiaId). + * + * @throws BrandNotFoundException */ - public function resolve(ArcadiaBrand $arcadiaBrand): Brand + public function resolve(ArcadiaId $arcadiaBrandCode): Brand { - $existingBrand = $this->brandRepository->findByArcadiaId($arcadiaBrand->id); + $existingBrand = $this->brandRepository->findByArcadiaId($arcadiaBrandCode); if ($existingBrand !== null) { return $existingBrand; } + $arcadiaBrand = $this->arcadiaBrandDirectory->find($arcadiaBrandCode); + + if ($arcadiaBrand === null) { + throw BrandNotFoundException::forArcadiaId($arcadiaBrandCode); + } + do { $code = $this->brandCodeGenerator->generate($arcadiaBrand->name); } while ($this->brandRepository->findByCode($code) !== null); @@ -66,43 +73,4 @@ final readonly class BrandResolver return $brand; } - - /** - * Resolve or generate/persist a Brand entity given an ArcadiaId. - * - * @throws BrandNotFoundException - */ - public function resolveByArcadiaId(ArcadiaId $arcadiaId): Brand - { - $existingBrand = $this->brandRepository->findByArcadiaId($arcadiaId); - - if ($existingBrand !== null) { - return $existingBrand; - } - - $arcadiaBrand = $this->arcadiaBrandDirectory->find($arcadiaId); - - if ($arcadiaBrand === null) { - throw BrandNotFoundException::forArcadiaId($arcadiaId); - } - - return $this->resolve($arcadiaBrand); - } - - /** - * Resolve all brands known in Arcadia directory. - * - * @return list - */ - public function resolveAll(): array - { - $arcadiaBrands = $this->arcadiaBrandDirectory->findAll(); - $brands = []; - - foreach ($arcadiaBrands as $arcadiaBrand) { - $brands[] = $this->resolve($arcadiaBrand); - } - - return $brands; - } } diff --git a/src/Infra/Adapters/Directories/EloquentIncoSheetDirectory.php b/src/Infra/Adapters/Directories/EloquentIncoSheetDirectory.php index 2516073..6a5721e 100644 --- a/src/Infra/Adapters/Directories/EloquentIncoSheetDirectory.php +++ b/src/Infra/Adapters/Directories/EloquentIncoSheetDirectory.php @@ -5,11 +5,13 @@ declare(strict_types=1); namespace Infra\Adapters\Directories; use Domain\Models\Entities\Inco\IncoSheet; +use Domain\Models\ValueObjects\Brand\ArcadiaId; +use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Inco\Additive; use Domain\Models\ValueObjects\Inco\Allegations; use Domain\Models\ValueObjects\Inco\Allergen; -use Domain\Models\ValueObjects\Inco\Brand; use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoIdQuery; use Domain\Models\ValueObjects\Inco\IncoSheetFilter; @@ -21,12 +23,21 @@ use Domain\Models\ValueObjects\Inco\Nutriscore; use Domain\Models\ValueObjects\Inco\PortionAmount; use Domain\Models\ValueObjects\Inco\StorageCondition; use Domain\Ports\Directories\IncoSheetDirectory; +use Domain\Ports\Repositories\BrandRepository; +use Domain\Services\BrandResolver; use Infra\Database\Models\IncoSheet as IncoSheetModel; +use Shared\Helpers\DatesHelper; use Shared\Paginated; use Shared\Pagination; final class EloquentIncoSheetDirectory implements IncoSheetDirectory { + public function __construct( + private BrandResolver $brandResolver, + private BrandRepository $brandRepository, + ) { + } + #[\Override] public function findAll( Pagination $pagination, @@ -44,7 +55,8 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory } if (! empty($filter->brands)) { - $query->whereIn('brand->code', $filter->brands); + $resolvedBrands = $this->resolveBrandFilterCodes(...$filter->brands); + $query->whereIn('brand->code', $resolvedBrands); } } @@ -73,14 +85,15 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory public function search( IncoIdQuery $query, Pagination $pagination, - string ...$brands + BrandCode ...$brands ): Paginated { $dbQuery = IncoSheetModel::query(); $dbQuery->where('id', 'LIKE', $query->value); if (! empty($brands)) { - $dbQuery->whereIn('brand->code', $brands); + $resolvedBrands = $this->resolveBrandFilterCodes(...$brands); + $dbQuery->whereIn('brand->code', $resolvedBrands); } $total = (int) (clone $dbQuery)->count(); @@ -169,10 +182,18 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory { $raw = is_array($model->ingredients) ? $model->ingredients : []; - /** @var array> $ingredientsGroups */ - $ingredientsGroups = $raw; + /** @var array> $cleanIngredients */ + $cleanIngredients = []; - return new Ingredients($ingredientsGroups); + foreach ($raw as $key => $values) { + if (is_string($key) && is_array($values)) { + $cleanIngredients[$key] = array_values( + array_filter($values, static fn ($item): bool => is_string($item)) + ); + } + } + + return new Ingredients($cleanIngredients); } private function portionsFrom(IncoSheetModel $model): ?PortionAmount @@ -184,19 +205,22 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory } return new PortionAmount( - lowPart: $this->getFloat($raw, 'lowPart'), - highPart: $this->getFloat($raw, 'highPart'), - approximative: (bool) ($raw['approximative'] ?? false), + lowPart: $this->getInt($raw, 'lowPart'), + highPart: $this->getInt($raw, 'highPart'), + approximative: $this->getBool($raw, 'approximative'), ); } - /** @return \Domain\Models\ValueObjects\Inco\Allergen[] */ + /** + * @return array + */ private function allergensFrom(IncoSheetModel $model): array { - $allergensRaw = is_array($model->allergens) ? $model->allergens : []; - $allergens = []; + $raw = is_array($model->allergens) ? $model->allergens : []; - foreach ($allergensRaw as $a) { + $allergens = []; + + foreach ($raw as $a) { if (is_array($a) && isset($a['code'], $a['label']) && is_numeric($a['code']) && is_string($a['label'])) { $allergens[] = new Allergen((int) $a['code'], $a['label']); } @@ -205,13 +229,20 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory return $allergens; } - private function brandFrom(IncoSheetModel $model): Brand + private function brandFrom(IncoSheetModel $model): IncoBrand { - $raw = is_array($model->brand) ? $model->brand : []; + $raw = is_array($model->brand) ? $model->brand : []; + $rawCode = isset($raw['code']) && (is_string($raw['code']) || is_numeric($raw['code'])) + ? (string) $raw['code'] + : '1'; - return new Brand( - code: $this->getString($raw, 'code', 'M01'), - label: $this->getString($raw, 'label', 'Standard'), + $arcadiaId = new ArcadiaId(is_numeric($rawCode) ? (int) $rawCode : 1); + $brand = $this->brandResolver->resolve($arcadiaId); + + return new IncoBrand( + name: $brand->name, + code: $brand->code, + arcadiaBrandCode: $brand->arcadiaId, ); } @@ -238,9 +269,13 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory return null; } - return new Nutriscore( - grade: $this->getString($raw, 'grade', 'A'), - ); + $grade = $this->getString($raw, 'grade'); + + if ($grade === '') { + return null; + } + + return new Nutriscore($grade); } private function storageConditionFrom(IncoSheetModel $model): ?StorageCondition @@ -251,20 +286,27 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory return null; } - return new StorageCondition( - instructions: $this->getString($raw, 'instructions', ''), - ); + $instructions = $this->getString($raw, 'instructions'); + + if ($instructions === '') { + return null; + } + + return new StorageCondition($instructions); } - /** @return \Domain\Models\ValueObjects\Inco\Additive[] */ + /** + * @return array + */ private function additivesFrom(IncoSheetModel $model): array { - $additivesRaw = is_array($model->additives) ? $model->additives : []; - $additives = []; + $raw = is_array($model->additives) ? $model->additives : []; - foreach ($additivesRaw as $ad) { - if (is_array($ad) && isset($ad['code'], $ad['label']) && is_scalar($ad['code']) && is_scalar($ad['label'])) { - $additives[] = new Additive((string) $ad['code'], (string) $ad['label']); + $additives = []; + + foreach ($raw as $a) { + if (is_array($a) && isset($a['code'], $a['label']) && is_string($a['code']) && is_string($a['label'])) { + $additives[] = new Additive($a['code'], $a['label']); } } @@ -272,86 +314,97 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory } /** - * @param array $data + * @return array */ - private function getString(array $data, string $key, string $default = ''): string + private function resolveBrandFilterCodes(BrandCode ...$brandCodes): array { - $val = $data[$key] ?? null; + $resolved = []; - return is_scalar($val) ? (string) $val : $default; - } - - private function toNullableString(mixed $val): ?string - { - return is_scalar($val) ? (string) $val : null; - } - - /** - * @param array $data - * @return array - */ - private function getArrayOfStrings(array $data, string $key): array - { - $val = $data[$key] ?? null; - - if (! is_array($val)) { - return []; - } - - $result = []; - foreach ($val as $item) { - if (is_string($item)) { - $result[] = $item; + foreach ($brandCodes as $brandCode) { + $brand = $this->brandRepository->findByCode($brandCode); + if ($brand !== null) { + $resolved[] = $brand->arcadiaId->value; + $resolved[] = (string) $brand->arcadiaId->value; } } - return $result; - } - - /** - * @param array $data - */ - private function getInt(array $data, string $key, int $default = 0): int - { - $val = $data[$key] ?? null; - - return is_numeric($val) ? (int) $val : $default; - } - - /** - * @param array $data - */ - private function getFloat(array $data, string $key, float $default = 0.0): float - { - $val = $data[$key] ?? null; - - return is_numeric($val) ? (float) $val : $default; - } - - private function toDateTime(\DateTimeInterface | string $value): \DateTimeInterface - { - if ($value instanceof \DateTimeInterface) { - return $value; + if (empty($resolved) && ! empty($brandCodes)) { + return [0]; } - return new \DateTimeImmutable($value); - } - - private function toNullableDateTime(\DateTimeInterface | string | null $value): ?\DateTimeInterface - { - if ($value === null) { - return null; + $unique = []; + foreach ($resolved as $item) { + if (! in_array($item, $unique, true)) { + $unique[] = $item; + } } - return $this->toDateTime($value); + return $unique; } - private function toSqlColumn(IncoSheetSortColumn $sortBy): string + private function toSqlColumn(IncoSheetSortColumn $column): string { - return match ($sortBy) { + return match ($column) { IncoSheetSortColumn::incoSheetId => 'id', IncoSheetSortColumn::createdAt => 'created_at', IncoSheetSortColumn::updatedAt => 'updated_at', }; } + + /** + * @param array $data + * @return array + */ + private function getArrayOfStrings(array $data, string $key): array + { + if (! isset($data[$key]) || ! is_array($data[$key])) { + return []; + } + + return array_values( + array_filter($data[$key], static fn ($item): bool => is_string($item)) + ); + } + + /** + * @param array $data + */ + private function getString(array $data, string $key, string $default = ''): string + { + return isset($data[$key]) && is_string($data[$key]) ? $data[$key] : $default; + } + + /** + * @param array $data + */ + private function getInt(array $data, string $key, int $default = 0): int + { + return isset($data[$key]) && is_numeric($data[$key]) ? (int) $data[$key] : $default; + } + + /** + * @param array $data + */ + private function getBool(array $data, string $key, bool $default = false): bool + { + return isset($data[$key]) ? (bool) $data[$key] : $default; + } + + private function toNullableString(mixed $value): ?string + { + return is_string($value) && trim($value) !== '' ? $value : null; + } + + private function toNullableDateTime(mixed $value): ?\DateTimeInterface + { + if ($value instanceof \DateTimeInterface) { + return $value; + } + + if (is_string($value)) { + return DatesHelper::parseDateTime($value); + } + + return null; + } } diff --git a/src/Infra/Adapters/Directories/EloquentIncoShortSheetDirectory.php b/src/Infra/Adapters/Directories/EloquentIncoShortSheetDirectory.php index 5066928..f23aef8 100644 --- a/src/Infra/Adapters/Directories/EloquentIncoShortSheetDirectory.php +++ b/src/Infra/Adapters/Directories/EloquentIncoShortSheetDirectory.php @@ -5,12 +5,14 @@ declare(strict_types=1); namespace Infra\Adapters\Directories; use Domain\Models\Entities\Inco\IncoShortSheet; +use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoIdQuery; use Domain\Models\ValueObjects\Inco\IncoSheetFilter; use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn; use Domain\Models\ValueObjects\Inco\IncoSheetSorter; use Domain\Ports\Directories\IncoShortSheetDirectory; +use Domain\Ports\Repositories\BrandRepository; use Infra\Database\Models\IncoSheet as IncoSheetModel; use Shared\Helpers\DatesHelper; use Shared\Paginated; @@ -21,6 +23,11 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory /** @var list */ private const array SHORT_FIELDS = ['id', 'name', 'label', 'created_at', 'updated_at']; + public function __construct( + private BrandRepository $brandRepository, + ) { + } + #[\Override] public function findAll( Pagination $pagination, @@ -38,7 +45,8 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory } if (! empty($filter->brands)) { - $query->whereIn('brand->code', $filter->brands); + $resolvedBrands = $this->resolveBrandFilterCodes(...$filter->brands); + $query->whereIn('brand->code', $resolvedBrands); } } @@ -66,14 +74,15 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory public function search( IncoIdQuery $query, Pagination $pagination, - string ...$brands + BrandCode ...$brands ): Paginated { $dbQuery = IncoSheetModel::query()->select(self::SHORT_FIELDS); $dbQuery->where('id', 'LIKE', $query->value); if (! empty($brands)) { - $dbQuery->whereIn('brand->code', $brands); + $resolvedBrands = $this->resolveBrandFilterCodes(...$brands); + $dbQuery->whereIn('brand->code', $resolvedBrands); } $total = (int) (clone $dbQuery)->count(); @@ -107,6 +116,35 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory return $this->toDomain($model); } + /** + * @return array + */ + private function resolveBrandFilterCodes(BrandCode ...$brandCodes): array + { + $resolved = []; + + foreach ($brandCodes as $brandCode) { + $brand = $this->brandRepository->findByCode($brandCode); + if ($brand !== null) { + $resolved[] = $brand->arcadiaId->value; + $resolved[] = (string) $brand->arcadiaId->value; + } + } + + if (empty($resolved) && ! empty($brandCodes)) { + return [0]; + } + + $unique = []; + foreach ($resolved as $item) { + if (! in_array($item, $unique, true)) { + $unique[] = $item; + } + } + + return $unique; + } + private function toDomain(IncoSheetModel $model): IncoShortSheet { $rawId = $model->getAttribute('id'); diff --git a/src/Infra/Adapters/Directories/InMemoryIncoSheetDirectory.php b/src/Infra/Adapters/Directories/InMemoryIncoSheetDirectory.php index f11349d..73be5f5 100644 --- a/src/Infra/Adapters/Directories/InMemoryIncoSheetDirectory.php +++ b/src/Infra/Adapters/Directories/InMemoryIncoSheetDirectory.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Infra\Adapters\Directories; use Domain\Models\Entities\Inco\IncoSheet; +use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoIdQuery; use Domain\Models\ValueObjects\Inco\IncoSheetFilter; @@ -57,8 +58,17 @@ final class InMemoryIncoSheetDirectory implements IncoSheetDirectory } } - if (! empty($filter->brands) && ! in_array($sheet->brand->code, $filter->brands, true)) { - return false; + if (! empty($filter->brands)) { + $matched = false; + foreach ($filter->brands as $brandCode) { + if ($sheet->brand->code->value === $brandCode->value) { + $matched = true; + break; + } + } + if (! $matched) { + return false; + } } return true; @@ -88,7 +98,7 @@ final class InMemoryIncoSheetDirectory implements IncoSheetDirectory public function search( IncoIdQuery $query, Pagination $pagination, - string ...$brands, + BrandCode ...$brands, ): Paginated { $regex = '/^' . str_replace('%', '.*', preg_quote($query->value, '/')) . '$/'; @@ -97,8 +107,17 @@ final class InMemoryIncoSheetDirectory implements IncoSheetDirectory return false; } - if (! empty($brands) && ! in_array($sheet->brand->code, $brands, true)) { - return false; + if (! empty($brands)) { + $matched = false; + foreach ($brands as $brandCode) { + if ($sheet->brand->code->value === $brandCode->value) { + $matched = true; + break; + } + } + if (! $matched) { + return false; + } } return true; diff --git a/src/Infra/Adapters/Directories/InMemoryIncoShortSheetDirectory.php b/src/Infra/Adapters/Directories/InMemoryIncoShortSheetDirectory.php index 85ab1a5..ad0afe9 100644 --- a/src/Infra/Adapters/Directories/InMemoryIncoShortSheetDirectory.php +++ b/src/Infra/Adapters/Directories/InMemoryIncoShortSheetDirectory.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Infra\Adapters\Directories; use Domain\Models\Entities\Inco\IncoShortSheet; +use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoIdQuery; use Domain\Models\ValueObjects\Inco\IncoSheetFilter; @@ -84,7 +85,7 @@ final class InMemoryIncoShortSheetDirectory implements IncoShortSheetDirectory public function search( IncoIdQuery $query, Pagination $pagination, - string ...$brands, + BrandCode ...$brands, ): Paginated { $regex = '/^' . str_replace('%', '.*', preg_quote($query->value, '/')) . '$/'; diff --git a/src/Infra/Adapters/Repositories/EloquentBrandRepository.php b/src/Infra/Adapters/Repositories/EloquentBrandRepository.php index 37d708c..3344a43 100644 --- a/src/Infra/Adapters/Repositories/EloquentBrandRepository.php +++ b/src/Infra/Adapters/Repositories/EloquentBrandRepository.php @@ -11,14 +11,24 @@ use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Brand\BrandId; use Domain\Models\ValueObjects\Brand\BrandName; use Domain\Ports\Repositories\BrandRepository; +use Illuminate\Support\Facades\Cache; use Infra\Database\Models\Brand as BrandModel; use Ramsey\Uuid\Uuid; final class EloquentBrandRepository implements BrandRepository { + private const int CACHE_TTL_SECONDS = 86400; + #[\Override] public function find(BrandId $id): ?Brand { + $key = "brand:id:{$id->value}"; + $brand = $this->fromCacheData(Cache::get($key)); + + if ($brand !== null) { + return $brand; + } + $model = BrandModel::query() ->with('arcadiaBrand') ->find($id->value); @@ -27,12 +37,22 @@ final class EloquentBrandRepository implements BrandRepository return null; } - return $this->toDomain($model); + $brand = $this->toDomain($model); + Cache::put($key, $this->toCacheData($brand), self::CACHE_TTL_SECONDS); + + return $brand; } #[\Override] public function findByCode(BrandCode $code): ?Brand { + $key = "brand:code:{$code->value}"; + $brand = $this->fromCacheData(Cache::get($key)); + + if ($brand !== null) { + return $brand; + } + $model = BrandModel::query() ->with('arcadiaBrand') ->where('uwil_public_id', $code->value) @@ -42,12 +62,22 @@ final class EloquentBrandRepository implements BrandRepository return null; } - return $this->toDomain($model); + $brand = $this->toDomain($model); + Cache::put($key, $this->toCacheData($brand), self::CACHE_TTL_SECONDS); + + return $brand; } #[\Override] public function findByArcadiaId(ArcadiaId $arcadiaId): ?Brand { + $key = "brand:arcadia:{$arcadiaId->value}"; + $brand = $this->fromCacheData(Cache::get($key)); + + if ($brand !== null) { + return $brand; + } + $model = BrandModel::query() ->with('arcadiaBrand') ->where('arcadia_id', $arcadiaId->value) @@ -57,19 +87,43 @@ final class EloquentBrandRepository implements BrandRepository return null; } - return $this->toDomain($model); + $brand = $this->toDomain($model); + Cache::put($key, $this->toCacheData($brand), self::CACHE_TTL_SECONDS); + + return $brand; } #[\Override] public function findAll(): array { - return array_values( + $key = 'brand:all'; + $data = Cache::get($key); + + if (is_array($data)) { + $brands = []; + foreach ($data as $item) { + $brand = $this->fromCacheData($item); + if ($brand !== null) { + $brands[] = $brand; + } + } + if (! empty($brands) && count($brands) === count($data)) { + return $brands; + } + } + + $brands = array_values( BrandModel::query() ->with('arcadiaBrand') ->get() ->map(fn (BrandModel $model): Brand => $this->toDomain($model)) ->all() ); + + $cachedList = array_map(fn (Brand $brand): array => $this->toCacheData($brand), $brands); + Cache::put($key, $cachedList, self::CACHE_TTL_SECONDS); + + return $brands; } #[\Override] @@ -82,6 +136,8 @@ final class EloquentBrandRepository implements BrandRepository 'uwil_public_id' => $entity->code, ] ); + + $this->invalidateCache($entity); } #[\Override] @@ -94,6 +150,54 @@ final class EloquentBrandRepository implements BrandRepository if ($deleted === 0) { throw BrandNotFoundException::forBrand($entity->id()); } + + $this->invalidateCache($entity); + } + + private function invalidateCache(Brand $entity): void + { + Cache::forget('brand:all'); + Cache::forget("brand:id:{$entity->id()->value}"); + Cache::forget("brand:code:{$entity->code->value}"); + Cache::forget("brand:arcadia:{$entity->arcadiaId->value}"); + } + + /** + * @return array{id: string, name: string, code: string, arcadiaId: int} + */ + private function toCacheData(Brand $brand): array + { + return [ + 'id' => (string) $brand->id()->value, + 'name' => $brand->name->value, + 'code' => $brand->code->value, + 'arcadiaId' => $brand->arcadiaId->value, + ]; + } + + private function fromCacheData(mixed $data): ?Brand + { + if ( + ! is_array($data) + || ! isset($data['id'], $data['name'], $data['code'], $data['arcadiaId']) + || ! is_string($data['id']) + || ! is_string($data['name']) + || ! is_string($data['code']) + || ! is_int($data['arcadiaId']) + ) { + return null; + } + + try { + return Brand::hydrate( + id: new BrandId(Uuid::fromString($data['id'])), + name: new BrandName($data['name']), + code: new BrandCode($data['code']), + arcadiaId: new ArcadiaId($data['arcadiaId']), + ); + } catch (\Throwable) { + return null; + } } private function toDomain(BrandModel $model): Brand diff --git a/src/Presentation/Api/V1/Dto/IncoSheets/Parts/BrandDto.php b/src/Presentation/Api/V1/Dto/IncoSheets/Parts/BrandDto.php index 6dfe27e..2788561 100644 --- a/src/Presentation/Api/V1/Dto/IncoSheets/Parts/BrandDto.php +++ b/src/Presentation/Api/V1/Dto/IncoSheets/Parts/BrandDto.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Presentation\Api\V1\Dto\IncoSheets\Parts; -use Domain\Models\ValueObjects\Inco\Brand; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Presentation\Api\V1\Contracts\ApiResponseDto; final readonly class BrandDto implements ApiResponseDto @@ -15,11 +15,11 @@ final readonly class BrandDto implements ApiResponseDto ) { } - public static function fromDomain(Brand $brand): self + public static function fromDomain(IncoBrand $brand): self { return new self( - code: $brand->code, - label: $brand->label, + code: $brand->code->value, + label: $brand->name->value, ); } diff --git a/src/Presentation/Api/V1/Http/Requests/IncoSheets/IncoSheetsIndexRequest.php b/src/Presentation/Api/V1/Http/Requests/IncoSheets/IncoSheetsIndexRequest.php index 8451659..d203503 100644 --- a/src/Presentation/Api/V1/Http/Requests/IncoSheets/IncoSheetsIndexRequest.php +++ b/src/Presentation/Api/V1/Http/Requests/IncoSheets/IncoSheetsIndexRequest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Presentation\Api\V1\Http\Requests\IncoSheets; +use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Inco\IncoSheetFilter; use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn; use Domain\Models\ValueObjects\Inco\IncoSheetSorter; @@ -47,22 +48,22 @@ final class IncoSheetsIndexRequest extends FormRequest { $since = $this->filled('since') ? DatesHelper::parseDateTime($this->string('since')->value()) : null; - $brands = []; - $rawBrands = $this->array('brands'); + $brandCodes = []; + $rawBrands = $this->array('brands'); foreach ($rawBrands as $b) { - if (is_string($b) || is_numeric($b)) { - $val = trim((string) $b); + if (is_string($b)) { + $val = trim($b); if ($val !== '') { - $brands[] = $val; + $brandCodes[] = new BrandCode($val); } } } - if (empty($brands) && $since === null) { + if (empty($brandCodes) && $since === null) { return null; } - return new IncoSheetFilter($since, ...$brands); + return new IncoSheetFilter($since, ...$brandCodes); } public function sortBy(): IncoSheetSortColumn diff --git a/src/Presentation/Api/V1/Http/Requests/IncoSheets/IncoSheetsSearchRequest.php b/src/Presentation/Api/V1/Http/Requests/IncoSheets/IncoSheetsSearchRequest.php index 0b11e28..bee32b2 100644 --- a/src/Presentation/Api/V1/Http/Requests/IncoSheets/IncoSheetsSearchRequest.php +++ b/src/Presentation/Api/V1/Http/Requests/IncoSheets/IncoSheetsSearchRequest.php @@ -4,11 +4,13 @@ declare(strict_types=1); namespace Presentation\Api\V1\Http\Requests\IncoSheets; +use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Inco\IncoIdQuery; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; /** + * @method \Shared\Pagination pagination(int $defaultPageSize = 25) * @method \Uri\Rfc3986\Uri toUri() */ final class IncoSheetsSearchRequest extends FormRequest @@ -29,16 +31,16 @@ final class IncoSheetsSearchRequest extends FormRequest return new IncoIdQuery($this->string('incoIdLike')->value()); } - /** @return array */ + /** @return array */ public function brands(): array { $raw = $this->array('brands'); $result = []; foreach ($raw as $b) { - if (is_string($b) || is_numeric($b)) { - $val = trim((string) $b); + if (is_string($b)) { + $val = trim($b); if ($val !== '') { - $result[] = $val; + $result[] = new BrandCode($val); } } } diff --git a/tests/Feature/Api/V1/IncoSheets/IncoSheetsIndexTest.php b/tests/Feature/Api/V1/IncoSheets/IncoSheetsIndexTest.php index a926559..8305db1 100644 --- a/tests/Feature/Api/V1/IncoSheets/IncoSheetsIndexTest.php +++ b/tests/Feature/Api/V1/IncoSheets/IncoSheetsIndexTest.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace Tests\Feature\Api\V1\IncoSheets; +use Domain\Models\ValueObjects\Brand\ArcadiaId; +use Domain\Services\BrandResolver; +use Infra\Database\Models\ArcadiaBrand; use Infra\Database\Models\IncoSheet; describe('IncoSheets Index Feature API', function (): void { @@ -75,6 +78,42 @@ describe('IncoSheets Index Feature API', function (): void { ['updatedAt', 'Ascending'], ]); + it('returns 200 OK with matching sheets filtered by BrandCode', function (): void { + // GIVEN + /** @var \Tests\TestCase $test */ + $test = $this; + + ArcadiaBrand::query()->firstOrCreate(['code' => 504], ['libelle' => 'LE GAULOIS PROFESSIONNEL']); + $brand = app(BrandResolver::class)->resolve(new ArcadiaId(504)); + + IncoSheet::factory()->create([ + 'id' => '9999001', + 'name' => 'Produit Gaulois Pro', + 'brand' => ['code' => 504, 'label' => 'LE GAULOIS PROFESSIONNEL'], + ]); + + // WHEN + $response = $test->getJson("/api/v1/inco-sheets?brands[]={$brand->code->value}"); + + // THEN + $response->assertStatus(200); + $response->assertJsonPath('incoSheets.0.id', '9999001') + ->assertJsonPath('incoSheets.0.brand.code', $brand->code->value) + ->assertJsonPath('incoSheets.0.brand.label', 'LE GAULOIS PROFESSIONNEL'); + }); + + it('returns 422 Unprocessable Entity when brand code format is invalid', function (): void { + // GIVEN + /** @var \Tests\TestCase $test */ + $test = $this; + + // WHEN + $response = $test->getJson('/api/v1/inco-sheets?brands[]=invalid'); + + // THEN + $response->assertStatus(422); + }); + it('returns 200 OK with short form inco sheets when short parameter is true', function (): void { // GIVEN /** @var \Tests\TestCase $test */ diff --git a/tests/Feature/Api/V1/IncoSheets/IncoSheetsSearchTest.php b/tests/Feature/Api/V1/IncoSheets/IncoSheetsSearchTest.php index cb0f00f..89cc4ad 100644 --- a/tests/Feature/Api/V1/IncoSheets/IncoSheetsSearchTest.php +++ b/tests/Feature/Api/V1/IncoSheets/IncoSheetsSearchTest.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace Tests\Feature\Api\V1\IncoSheets; +use Domain\Models\ValueObjects\Brand\ArcadiaId; +use Domain\Services\BrandResolver; +use Infra\Database\Models\ArcadiaBrand; use Infra\Database\Models\IncoSheet; describe('IncoSheets Search Feature API', function (): void { @@ -11,13 +14,13 @@ describe('IncoSheets Search Feature API', function (): void { IncoSheet::factory()->create([ 'id' => '1000101', 'name' => 'Poulet Rôti 1kg', - 'brand' => ['code' => 'M01', 'label' => 'Standard'], + 'brand' => ['code' => 1, 'label' => 'Standard'], ]); IncoSheet::factory()->create([ 'id' => '1000102', 'name' => 'Poulet Fermier', - 'brand' => ['code' => 'M02', 'label' => 'Bio'], + 'brand' => ['code' => 2, 'label' => 'Bio'], ]); }); @@ -51,19 +54,36 @@ describe('IncoSheets Search Feature API', function (): void { $response->assertJsonCount(1, 'incoSheets'); }); - it('returns 200 OK with matching sheets filtered by brand', function (): void { + it('returns 200 OK with matching sheets filtered by BrandCode', function (): void { + // GIVEN + /** @var \Tests\TestCase $test */ + $test = $this; + $query = urlencode('%10001%'); + + ArcadiaBrand::query()->firstOrCreate(['code' => 1], ['libelle' => 'Standard']); + $brand = app(BrandResolver::class)->resolve(new ArcadiaId(1)); + + // WHEN + $response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}&brands[]={$brand->code->value}"); + + // THEN + $response->assertStatus(200); + $response->assertJsonCount(1, 'incoSheets'); + $response->assertJsonPath('incoSheets.0.id', '1000101') + ->assertJsonPath('incoSheets.0.brand.code', $brand->code->value); + }); + + it('returns 422 Unprocessable Entity when brand code format is invalid', function (): void { // GIVEN /** @var \Tests\TestCase $test */ $test = $this; $query = urlencode('%10001%'); // WHEN - $response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}&brands[]=M01"); + $response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}&brands[]=invalid"); // THEN - $response->assertStatus(200); - $response->assertJsonCount(1, 'incoSheets'); - $response->assertJsonPath('incoSheets.0.id', '1000101'); + $response->assertStatus(422); }); it('returns 200 OK with short form inco sheets when short parameter is true', function (): void { diff --git a/tests/Unit/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrandsTest.php b/tests/Unit/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrandsTest.php index 99a3bd2..6be4f19 100644 --- a/tests/Unit/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrandsTest.php +++ b/tests/Unit/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrandsTest.php @@ -56,7 +56,7 @@ describe('FindAllBrands Usecase Unit Tests', function (): void { $brandRepository->add($existingBrand); $resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger); - $usecase = new FindAllBrands($resolver); + $usecase = new FindAllBrands($arcadiaDirectory, $resolver); $input = new Input(Pagination::ofPage(1, 25)); // WHEN @@ -91,7 +91,7 @@ describe('FindAllBrands Usecase Unit Tests', function (): void { $arcadiaDirectory->add($arcadia1, $arcadia2, $arcadia3); $resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger); - $usecase = new FindAllBrands($resolver); + $usecase = new FindAllBrands($arcadiaDirectory, $resolver); $input = new Input(Pagination::ofPage(2, 2)); // WHEN @@ -108,7 +108,7 @@ describe('FindAllBrands Usecase Unit Tests', function (): void { it('returns an empty list when there are no arcadia brands', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void { // GIVEN $resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger); - $usecase = new FindAllBrands($resolver); + $usecase = new FindAllBrands($arcadiaDirectory, $resolver); $input = new Input(Pagination::ofPage(1, 25)); // WHEN diff --git a/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindAllIncoSheetsDtoTest.php b/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindAllIncoSheetsDtoTest.php index cfdfc9a..87da88e 100644 --- a/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindAllIncoSheetsDtoTest.php +++ b/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindAllIncoSheetsDtoTest.php @@ -10,8 +10,11 @@ use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedIncoSheet; use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedShortIncoSheet; use Domain\Models\Entities\Inco\IncoSheet; use Domain\Models\Entities\Inco\IncoShortSheet; +use Domain\Models\ValueObjects\Brand\ArcadiaId; +use Domain\Models\ValueObjects\Brand\BrandCode; +use Domain\Models\ValueObjects\Brand\BrandName; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn; use Domain\Models\ValueObjects\Inco\IncoSheetSorter; @@ -55,7 +58,7 @@ describe('FindAllIncoSheets DTOs Instantiation & Logic', function (): void { ingredients: new Ingredients(['main' => ['Viande']]), portions: null, allergens: [], - brand: new Brand('M01', 'Le Gaulois'), + brand: new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(1)), isSeasonal: true, weight: null, nutriscore: null, diff --git a/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindAllIncoSheetsTest.php b/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindAllIncoSheetsTest.php index ba89db3..72f2068 100644 --- a/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindAllIncoSheetsTest.php +++ b/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindAllIncoSheetsTest.php @@ -10,8 +10,11 @@ use Application\Arcadia\Usecase\IncoSheets\FindAll\Output; use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedIncoSheet; use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedShortIncoSheet; use Domain\Models\Entities\Inco\IncoSheet; +use Domain\Models\ValueObjects\Brand\ArcadiaId; +use Domain\Models\ValueObjects\Brand\BrandCode; +use Domain\Models\ValueObjects\Brand\BrandName; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn; use Domain\Models\ValueObjects\Inco\IncoSheetSorter; @@ -53,7 +56,7 @@ describe('FindAllIncoSheets Usecase Unit Tests', function (): void { ingredients: new Ingredients(['Ingrédients' => ['Viande de dinde (66%)']]), portions: null, allergens: [], - brand: new Brand('M01', 'Le Gaulois'), + brand: new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(1)), isSeasonal: true, weight: null, nutriscore: null, diff --git a/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindIncoSheetDtoTest.php b/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindIncoSheetDtoTest.php index f6dca83..a0c0350 100644 --- a/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindIncoSheetDtoTest.php +++ b/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindIncoSheetDtoTest.php @@ -6,8 +6,11 @@ namespace Tests\Unit\Application\Arcadia\Usecase\IncoSheets; use Application\Arcadia\Usecase\IncoSheets\Find\Input; use Application\Arcadia\Usecase\IncoSheets\Find\Output; +use Domain\Models\ValueObjects\Brand\ArcadiaId; +use Domain\Models\ValueObjects\Brand\BrandCode; +use Domain\Models\ValueObjects\Brand\BrandName; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\Ingredients; @@ -43,7 +46,7 @@ describe('FindIncoSheet DTO Instantiation', function (): void { ingredients: new Ingredients(['main' => ['Viande']]), portions: null, allergens: [], - brand: new Brand('M01', 'Le Gaulois'), + brand: new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(1)), isSeasonal: true, weight: null, nutriscore: null, diff --git a/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindIncoSheetTest.php b/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindIncoSheetTest.php index 0c20b86..6c5ed9b 100644 --- a/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindIncoSheetTest.php +++ b/tests/Unit/Application/Arcadia/Usecase/Incosheets/FindIncoSheetTest.php @@ -9,8 +9,11 @@ use Application\Arcadia\Usecase\IncoSheets\Find\FindIncoSheet; use Application\Arcadia\Usecase\IncoSheets\Find\Input; use Application\Arcadia\Usecase\IncoSheets\Find\Output; use Domain\Models\Entities\Inco\IncoSheet; +use Domain\Models\ValueObjects\Brand\ArcadiaId; +use Domain\Models\ValueObjects\Brand\BrandCode; +use Domain\Models\ValueObjects\Brand\BrandName; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\Ingredients; use Infra\Adapters\Directories\InMemoryIncoSheetDirectory; @@ -44,7 +47,7 @@ describe('FindIncoSheet Usecase Unit Tests', function (): void { ingredients: new Ingredients(['Ingrédients' => ['Viande de dinde (66%)']]), portions: null, allergens: [], - brand: new Brand('M01', 'Le Gaulois'), + brand: new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(1)), isSeasonal: true, weight: null, nutriscore: null, @@ -67,7 +70,8 @@ describe('FindIncoSheet Usecase Unit Tests', function (): void { ->and($result->id)->toBe($incoId) ->and($result->name)->toBe('Poulet Rôti 1kg') ->and($result->label)->toBe('Poulet Rôti Classe A') - ->and($result->brand->label)->toBe('Le Gaulois') + ->and($result->brand->name->value)->toBe('Le Gaulois') + ->and($result->brand->code->value)->toBe('LGL01') ->and($result->isSeasonal)->toBeTrue(); }); diff --git a/tests/Unit/Application/Arcadia/Usecase/Incosheets/SearchIncoSheetsTest.php b/tests/Unit/Application/Arcadia/Usecase/Incosheets/SearchIncoSheetsTest.php index 9b46e3a..1621d84 100644 --- a/tests/Unit/Application/Arcadia/Usecase/Incosheets/SearchIncoSheetsTest.php +++ b/tests/Unit/Application/Arcadia/Usecase/Incosheets/SearchIncoSheetsTest.php @@ -11,8 +11,11 @@ use Application\Arcadia\Usecase\IncoSheets\Search\PaginatedShortIncoSheet; use Application\Arcadia\Usecase\IncoSheets\Search\SearchIncoSheets; use Domain\Models\Entities\Inco\IncoSheet; use Domain\Models\Entities\Inco\IncoShortSheet; +use Domain\Models\ValueObjects\Brand\ArcadiaId; +use Domain\Models\ValueObjects\Brand\BrandCode; +use Domain\Models\ValueObjects\Brand\BrandName; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoIdQuery; use Domain\Models\ValueObjects\Inco\Ingredients; @@ -52,7 +55,7 @@ describe('SearchIncoSheets Usecase Unit Tests', function (): void { ingredients: new Ingredients(['Ingrédients' => ['Viande de dinde (66%)']]), portions: null, allergens: [], - brand: new Brand('M01', 'Le Gaulois'), + brand: new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(1)), isSeasonal: true, weight: null, nutriscore: null, @@ -69,7 +72,7 @@ describe('SearchIncoSheets Usecase Unit Tests', function (): void { query: new IncoIdQuery('12%'), short: false, pagination: Pagination::ofPage(1, 10), - brands: 'M01', + brands: new BrandCode('LGL01'), ); // WHEN diff --git a/tests/Unit/Domain/Entities/IncoSheetEntityTest.php b/tests/Unit/Domain/Entities/IncoSheetEntityTest.php index 4a9e2b8..011937e 100644 --- a/tests/Unit/Domain/Entities/IncoSheetEntityTest.php +++ b/tests/Unit/Domain/Entities/IncoSheetEntityTest.php @@ -5,8 +5,11 @@ declare(strict_types=1); namespace Tests\Unit\Domain\Entities; use Domain\Models\Entities\Inco\IncoSheet; +use Domain\Models\ValueObjects\Brand\ArcadiaId; +use Domain\Models\ValueObjects\Brand\BrandCode; +use Domain\Models\ValueObjects\Brand\BrandName; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\Ingredients; use Shared\DDD\Model\Entity; @@ -19,7 +22,7 @@ describe('IncoSheet Domain Entity Unit Tests', function (): void { $id = new IncoId('12345'); $allegations = new Allegations(['Volaille française']); $ingredients = new Ingredients(['Ingrédients' => ['Viande de dinde (66%)']]); - $brand = new Brand('M01', 'Le Gaulois'); + $brand = new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(504)); // WHEN $sheet = IncoSheet::hydrate( @@ -50,7 +53,9 @@ describe('IncoSheet Domain Entity Unit Tests', function (): void { expect($sheet)->toBeInstanceOf(Entity::class) ->and($sheet->id())->toBe($id) ->and($sheet->name)->toBe('Poulet Rôti 1kg') - ->and($sheet->brand->label)->toBe('Le Gaulois') + ->and($sheet->brand->name->value)->toBe('Le Gaulois') + ->and($sheet->brand->code->value)->toBe('LGL01') + ->and($sheet->brand->arcadiaBrandCode->value)->toBe(504) ->and($sheet->isSeasonal)->toBeTrue(); }); }); diff --git a/tests/Unit/Domain/Models/ValueObjects/Inco/BrandTest.php b/tests/Unit/Domain/Models/ValueObjects/Inco/BrandTest.php deleted file mode 100644 index 6a409c1..0000000 --- a/tests/Unit/Domain/Models/ValueObjects/Inco/BrandTest.php +++ /dev/null @@ -1,38 +0,0 @@ -code)->toBe('M01') - ->and($brand->label)->toBe('Le Gaulois') - ->and($brand->code)->toBe('M01') - ->and($brand->label)->toBe('Le Gaulois'); - }); - - it('throws DomainException when code is empty', function (): void { - // GIVEN / WHEN / THEN - expect(fn () => new Brand(' ', 'Le Gaulois')) - ->toThrow(\DomainException::class, 'Brand code must not be empty.'); - }); - - it('throws DomainException when label is empty', function (): void { - // GIVEN / WHEN / THEN - expect(fn () => new Brand('M01', ' ')) - ->toThrow(\DomainException::class, 'Brand label must not be empty.'); - }); -}); diff --git a/tests/Unit/Domain/Models/ValueObjects/Inco/IncoBrandTest.php b/tests/Unit/Domain/Models/ValueObjects/Inco/IncoBrandTest.php new file mode 100644 index 0000000..15eb1c2 --- /dev/null +++ b/tests/Unit/Domain/Models/ValueObjects/Inco/IncoBrandTest.php @@ -0,0 +1,29 @@ +name->value)->toBe('Le Gaulois') + ->and($brand->code->value)->toBe('LGL01') + ->and($brand->arcadiaBrandCode->value)->toBe(504); + }); +}); diff --git a/tests/Unit/Domain/Models/ValueObjects/Inco/IncoSheetFilterTest.php b/tests/Unit/Domain/Models/ValueObjects/Inco/IncoSheetFilterTest.php index 239f0c7..355a539 100644 --- a/tests/Unit/Domain/Models/ValueObjects/Inco/IncoSheetFilterTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/Inco/IncoSheetFilterTest.php @@ -4,34 +4,30 @@ declare(strict_types=1); namespace Tests\Unit\Domain\Models\ValueObjects\Inco; +use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Inco\IncoSheetFilter; covers(IncoSheetFilter::class); describe('IncoSheetFilter Value Object', function (): void { - it('can be instantiated with brand list filter', function (): void { + it('can be instantiated with BrandCode list filter', function (): void { // GIVEN - $brands = ['M01', 'M02']; + $code1 = new BrandCode('LGL01'); + $code2 = new BrandCode('MCQ01'); // WHEN - $filter = new IncoSheetFilter(null, ...$brands); + $filter = new IncoSheetFilter(null, $code1, $code2); // THEN - expect($filter->brands)->toBe(['M01', 'M02']); + expect($filter->brands)->toHaveCount(2) + ->and($filter->brands[0])->toBe($code1) + ->and($filter->brands[1])->toBe($code2); }); it('throws DomainException when instantiated with all null parameters', function (): void { // GIVEN, WHEN & THEN expect(fn () => new IncoSheetFilter()) + // THENE ->toThrow(\DomainException::class, 'IncoSheetFilter requires at least one filtering criterion.'); }); - - it('throws InvalidArgumentException when brands array contains an empty string', function (): void { - // GIVEN - $invalidBrands = ['M01', '']; - - // WHEN & THEN - expect(fn () => new IncoSheetFilter(null, ...$invalidBrands)) - ->toThrow(\InvalidArgumentException::class, 'Every item in brands array must be a non empty string.'); - }); }); diff --git a/tests/Unit/Domain/Services/BrandResolverTest.php b/tests/Unit/Domain/Services/BrandResolverTest.php index 0f01825..10f022a 100644 --- a/tests/Unit/Domain/Services/BrandResolverTest.php +++ b/tests/Unit/Domain/Services/BrandResolverTest.php @@ -41,23 +41,22 @@ describe('BrandResolver Unit Tests', function (): void { ); $brandRepository->add($existing); - $resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger); - $arcadiaBrand = new ArcadiaBrand(new ArcadiaId(1), new BrandName('Le Gaulois')); + $resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger); // WHEN - $resolved = $resolver->resolve($arcadiaBrand); + $resolved = $resolver->resolve(new ArcadiaId(1)); // THEN expect($resolved)->toBe($existing); }); - it('generates, persists and logs a new brand if not in repository', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void { + it('generates, persists and logs a new brand if not in repository but in Arcadia directory', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void { // GIVEN - $resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger); - $arcadiaBrand = new ArcadiaBrand(new ArcadiaId(2), new BrandName('Maître CoQ')); + $arcadiaDirectory->add(new ArcadiaBrand(new ArcadiaId(2), new BrandName('Maître CoQ'))); + $resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger); // WHEN - $resolved = $resolver->resolve($arcadiaBrand); + $resolved = $resolver->resolve(new ArcadiaId(2)); // THEN expect($resolved->arcadiaId->value)->toBe(2) @@ -67,44 +66,12 @@ describe('BrandResolver Unit Tests', function (): void { expect($brandRepository->findByArcadiaId(new ArcadiaId(2)))->not->toBeNull(); }); - it('resolves by ArcadiaId directly', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void { - // GIVEN - $arcadiaBrand = new ArcadiaBrand(new ArcadiaId(3), new BrandName('Loué')); - $arcadiaDirectory->add($arcadiaBrand); - - $resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger); - - // WHEN - $resolved = $resolver->resolveByArcadiaId(new ArcadiaId(3)); - - // THEN - expect($resolved->arcadiaId->value)->toBe(3) - ->and($resolved->name->value)->toBe('Loué'); - }); - - it('throws BrandNotFoundException when resolving by unknown ArcadiaId', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void { + it('throws BrandNotFoundException when resolving an unknown ArcadiaId', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void { // GIVEN $resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger); // WHEN & THEN - expect(fn () => $resolver->resolveByArcadiaId(new ArcadiaId(999))) + expect(fn () => $resolver->resolve(new ArcadiaId(999))) ->toThrow(BrandNotFoundException::class, 'Arcadia brand with ID 999 not found.'); }); - - it('resolves all brands from Arcadia directory', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void { - // GIVEN - $arcadiaDirectory->add( - new ArcadiaBrand(new ArcadiaId(1), new BrandName('Le Gaulois')), - new ArcadiaBrand(new ArcadiaId(2), new BrandName('Maître CoQ')), - ); - - $resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger); - - // WHEN - $resolvedList = $resolver->resolveAll(); - - // THEN - expect($resolvedList)->toHaveCount(2) - ->and($brandRepository->findAll())->toHaveCount(2); - }); }); diff --git a/tests/Unit/Infra/Adapters/Directories/InMemoryIncoSheetDirectoryTest.php b/tests/Unit/Infra/Adapters/Directories/InMemoryIncoSheetDirectoryTest.php index 62e07e4..59903d0 100644 --- a/tests/Unit/Infra/Adapters/Directories/InMemoryIncoSheetDirectoryTest.php +++ b/tests/Unit/Infra/Adapters/Directories/InMemoryIncoSheetDirectoryTest.php @@ -5,8 +5,11 @@ declare(strict_types=1); namespace Tests\Unit\Infra\Adapters\Directories; use Domain\Models\Entities\Inco\IncoSheet; +use Domain\Models\ValueObjects\Brand\ArcadiaId; +use Domain\Models\ValueObjects\Brand\BrandCode; +use Domain\Models\ValueObjects\Brand\BrandName; use Domain\Models\ValueObjects\Inco\Allegations; -use Domain\Models\ValueObjects\Inco\Brand; +use Domain\Models\ValueObjects\Inco\IncoBrand; use Domain\Models\ValueObjects\Inco\IncoId; use Domain\Models\ValueObjects\Inco\IncoIdQuery; use Domain\Models\ValueObjects\Inco\IncoSheetFilter; @@ -19,7 +22,7 @@ use Shared\Pagination; covers(InMemoryIncoSheetDirectory::class); describe('InMemoryIncoSheetDirectory Unit Tests', function (): void { - function createSheet(string $idStr, string $brandCode, string $brandLabel): IncoSheet + function createSheet(string $idStr, string $brandCode, string $brandLabel, int $arcadiaId = 1): IncoSheet { return IncoSheet::hydrate( id: new IncoId($idStr), @@ -34,7 +37,7 @@ describe('InMemoryIncoSheetDirectory Unit Tests', function (): void { ingredients: new Ingredients(['Ingrédients' => ['Test']]), portions: null, allergens: [], - brand: new Brand($brandCode, $brandLabel), + brand: new IncoBrand(new BrandName($brandLabel), new BrandCode($brandCode), new ArcadiaId($arcadiaId)), isSeasonal: true, weight: null, nutriscore: null, @@ -49,7 +52,7 @@ describe('InMemoryIncoSheetDirectory Unit Tests', function (): void { it('finds an inco sheet by id', function (): void { // GIVEN $directory = new InMemoryIncoSheetDirectory(); - $sheet = createSheet('12345', 'M01', 'Le Gaulois'); + $sheet = createSheet('12345', 'LGL01', 'Le Gaulois', 1); $directory->add($sheet); // WHEN @@ -64,13 +67,13 @@ describe('InMemoryIncoSheetDirectory Unit Tests', function (): void { it('filters, sorts and paginates inco sheets', function (): void { // GIVEN $directory = new InMemoryIncoSheetDirectory(); - $sheet1 = createSheet('10001', 'M01', 'Le Gaulois'); - $sheet2 = createSheet('10002', 'M02', 'Maître CoQ'); + $sheet1 = createSheet('10001', 'LGL01', 'Le Gaulois', 1); + $sheet2 = createSheet('10002', 'MCQ01', 'Maître CoQ', 2); $directory->add($sheet1, $sheet2); $pagination = Pagination::ofPage(1, 10); $sorter = new IncoSheetSorter(IncoSheetSortColumn::incoSheetId, \SortDirection::Descending); - $filter = new IncoSheetFilter(null, 'M01'); + $filter = new IncoSheetFilter(null, new BrandCode('LGL01')); // WHEN $result = $directory->findAll($pagination, $sorter, $filter); @@ -84,16 +87,16 @@ describe('InMemoryIncoSheetDirectory Unit Tests', function (): void { it('searches inco sheets by id query and brands', function (): void { // GIVEN $directory = new InMemoryIncoSheetDirectory(); - $sheet1 = createSheet('12345', 'M01', 'Le Gaulois'); - $sheet2 = createSheet('12999', 'M01', 'Le Gaulois'); - $sheet3 = createSheet('54321', 'M02', 'Maître CoQ'); + $sheet1 = createSheet('12345', 'LGL01', 'Le Gaulois', 1); + $sheet2 = createSheet('12999', 'LGL01', 'Le Gaulois', 1); + $sheet3 = createSheet('54321', 'MCQ01', 'Maître CoQ', 2); $directory->add($sheet1, $sheet2, $sheet3); $query = new IncoIdQuery('12%'); $pagination = Pagination::ofPage(1, 10); // WHEN - $result = $directory->search($query, $pagination, 'M01'); + $result = $directory->search($query, $pagination, new BrandCode('LGL01')); // THEN expect($result->getTotal())->toBe(2) @@ -103,7 +106,7 @@ describe('InMemoryIncoSheetDirectory Unit Tests', function (): void { it('clears all sheets', function (): void { // GIVEN $directory = new InMemoryIncoSheetDirectory(); - $sheet = createSheet('12345', 'M01', 'Le Gaulois'); + $sheet = createSheet('12345', 'LGL01', 'Le Gaulois', 1); $directory->add($sheet); // WHEN