diff --git a/src/Domain/Models/Entities/Coupon.php b/src/Domain/Models/Entities/Coupon.php index 347e3d0..07d1988 100644 --- a/src/Domain/Models/Entities/Coupon.php +++ b/src/Domain/Models/Entities/Coupon.php @@ -8,6 +8,7 @@ use Domain\Models\ValueObjects\Actor\ActorId; use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Coupon\CouponAvailabilityCalendar; use Domain\Models\ValueObjects\Coupon\CouponCode; +use Domain\Models\ValueObjects\Coupon\CouponCountryCoverage; use Domain\Models\ValueObjects\Coupon\CouponDiffusion; use Domain\Models\ValueObjects\Coupon\CouponId; use Domain\Models\ValueObjects\Coupon\CouponLegalInformationArray; @@ -45,6 +46,11 @@ final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier public ?CouponAvailabilityCalendar $availabilityCalendar = null, public ?\DateTimeInterface $updatedAt = null, ) { + CouponCountryCoverage::assert( + $this->processingCode, + $this->validityInformations, + $this->legalInformations, + ); } public function id(): CouponId diff --git a/src/Domain/Models/ValueObjects/Coupon/CouponCountryCoverage.php b/src/Domain/Models/ValueObjects/Coupon/CouponCountryCoverage.php new file mode 100644 index 0000000..7a33d25 --- /dev/null +++ b/src/Domain/Models/ValueObjects/Coupon/CouponCountryCoverage.php @@ -0,0 +1,42 @@ +value as $processingCode) { + $country = $processingCode->availableIn; + + if (! self::hasInformationFor($country, $validityInformations)) { + throw new \DomainException(sprintf('A coupon requires validity information for %s.', $country->name)); + } + + if (! self::hasInformationFor($country, $legalInformations)) { + throw new \DomainException(sprintf('A coupon requires legal information for %s.', $country->name)); + } + } + } + + private static function hasInformationFor( + Country $country, + CouponValidityInformationArray | CouponLegalInformationArray $informations, + ): bool { + return array_any( + $informations->value, + fn (CouponValidityInformation | CouponLegalInformation $information): bool => $information->forLocale === $country, + ); + } +} diff --git a/src/Domain/Models/ValueObjects/Coupon/CouponLegalInformationArray.php b/src/Domain/Models/ValueObjects/Coupon/CouponLegalInformationArray.php index 9fd1189..0925404 100644 --- a/src/Domain/Models/ValueObjects/Coupon/CouponLegalInformationArray.php +++ b/src/Domain/Models/ValueObjects/Coupon/CouponLegalInformationArray.php @@ -17,12 +17,22 @@ final readonly class CouponLegalInformationArray } $hasFrance = false; + $countries = []; foreach ($this->value as $couponLegalInformation) { if (! $couponLegalInformation instanceof CouponLegalInformation) { throw new \DomainException('Value of CouponLegalInformationArray must be CouponLegalInformation instances.'); } + if (isset($countries[$couponLegalInformation->forLocale->name])) { + throw new \DomainException(sprintf( + 'Only one CouponLegalInformation is allowed per country, duplicate for %s.', + $couponLegalInformation->forLocale->name, + )); + } + + $countries[$couponLegalInformation->forLocale->name] = true; + if ($couponLegalInformation->forLocale === Country::FRANCE) { $hasFrance = true; } diff --git a/src/Domain/Models/ValueObjects/Coupon/CouponMovementArray.php b/src/Domain/Models/ValueObjects/Coupon/CouponMovementArray.php index 7cfda38..7552374 100644 --- a/src/Domain/Models/ValueObjects/Coupon/CouponMovementArray.php +++ b/src/Domain/Models/ValueObjects/Coupon/CouponMovementArray.php @@ -8,13 +8,13 @@ final readonly class CouponMovementArray { public function __construct( /** @var CouponMovement[] */ - public array $couponMovements, + public array $value, ) { - if ($this->couponMovements === []) { + if ($this->value === []) { throw new \DomainException('CouponMovementArray requires at least one CouponMovement.'); } - foreach ($this->couponMovements as $couponMovement) { + foreach ($this->value as $couponMovement) { if (! $couponMovement instanceof CouponMovement) { throw new \DomainException('Value of CouponMovementArray must be CouponMovement instances.'); } diff --git a/src/Domain/Models/ValueObjects/Coupon/CouponProcessingCodeArray.php b/src/Domain/Models/ValueObjects/Coupon/CouponProcessingCodeArray.php index a07baff..7249c40 100644 --- a/src/Domain/Models/ValueObjects/Coupon/CouponProcessingCodeArray.php +++ b/src/Domain/Models/ValueObjects/Coupon/CouponProcessingCodeArray.php @@ -17,12 +17,22 @@ final readonly class CouponProcessingCodeArray } $hasFrance = false; + $countries = []; foreach ($this->value as $processingCode) { if (! $processingCode instanceof CouponProcessingCode) { throw new \DomainException('Value of CouponProcessingCodeArray must be CouponProcessingCode instances.'); } + if (isset($countries[$processingCode->availableIn->name])) { + throw new \DomainException(sprintf( + 'Only one CouponProcessingCode is allowed per country, duplicate for %s.', + $processingCode->availableIn->name, + )); + } + + $countries[$processingCode->availableIn->name] = true; + if ($processingCode->availableIn === Country::FRANCE) { $hasFrance = true; } diff --git a/src/Domain/Models/ValueObjects/Coupon/CouponValidityInformationArray.php b/src/Domain/Models/ValueObjects/Coupon/CouponValidityInformationArray.php index 23e415d..d1a9ff8 100644 --- a/src/Domain/Models/ValueObjects/Coupon/CouponValidityInformationArray.php +++ b/src/Domain/Models/ValueObjects/Coupon/CouponValidityInformationArray.php @@ -17,13 +17,23 @@ final readonly class CouponValidityInformationArray } $hasFrance = false; + $countries = []; - foreach ($this->value as $couponLegalInformation) { - if (! $couponLegalInformation instanceof CouponValidityInformation) { + foreach ($this->value as $couponValidityInformation) { + if (! $couponValidityInformation instanceof CouponValidityInformation) { throw new \DomainException('Value of CouponValidityInformationArray must be CouponValidityInformation instances.'); } - if ($couponLegalInformation->forLocale === Country::FRANCE) { + if (isset($countries[$couponValidityInformation->forLocale->name])) { + throw new \DomainException(sprintf( + 'Only one CouponValidityInformation is allowed per country, duplicate for %s.', + $couponValidityInformation->forLocale->name, + )); + } + + $countries[$couponValidityInformation->forLocale->name] = true; + + if ($couponValidityInformation->forLocale === Country::FRANCE) { $hasFrance = true; } } diff --git a/src/Domain/Models/ValueObjects/RequestedCoupon/RequestedCoupon.php b/src/Domain/Models/ValueObjects/RequestedCoupon/RequestedCoupon.php index 17f67c3..f88965f 100644 --- a/src/Domain/Models/ValueObjects/RequestedCoupon/RequestedCoupon.php +++ b/src/Domain/Models/ValueObjects/RequestedCoupon/RequestedCoupon.php @@ -4,19 +4,16 @@ declare(strict_types=1); namespace Domain\Models\ValueObjects\RequestedCoupon; -use Domain\Models\ValueObjects\Coupon\CouponLegalInformation; +use Domain\Models\ValueObjects\Coupon\CouponCountryCoverage; use Domain\Models\ValueObjects\Coupon\CouponLegalInformationArray; use Domain\Models\ValueObjects\Coupon\CouponName; use Domain\Models\ValueObjects\Coupon\CouponPicture; -use Domain\Models\ValueObjects\Coupon\CouponProcessingCode; use Domain\Models\ValueObjects\Coupon\CouponProcessingCodeArray; use Domain\Models\ValueObjects\Coupon\CouponProductArray; -use Domain\Models\ValueObjects\Coupon\CouponValidityInformation; use Domain\Models\ValueObjects\Coupon\CouponValidityInformationArray; use Domain\Models\ValueObjects\MoneyAmount; use Domain\Models\ValueObjects\Period; use Domain\Models\ValueObjects\PrintRules; -use Shared\Enums\Country; final readonly class RequestedCoupon { @@ -32,39 +29,10 @@ final readonly class RequestedCoupon public CouponProductArray $couponProducts, public ?PrintRules $printRules = null, ) { - if ($this->hasProcessingCodeFor(Country::BENELUX)) { - $this->assertCountryIsFullyCovered(Country::BENELUX); - } - } - - private function assertCountryIsFullyCovered(Country $country): void - { - if (! $this->hasProcessingCodeFor($country)) { - throw new \DomainException(sprintf('A coupon requires a processing code for %s.', $country->name)); - } - - if (! $this->hasInformationFor($country, $this->couponValidityInformations)) { - throw new \DomainException(sprintf('A coupon requires validity information for %s.', $country->name)); - } - - if (! $this->hasInformationFor($country, $this->couponLegalInformations)) { - throw new \DomainException(sprintf('A coupon requires legal information for %s.', $country->name)); - } - } - - private function hasProcessingCodeFor(Country $country): bool - { - return array_any( - $this->couponProcessingCodes->value, - fn (CouponProcessingCode $processingCode): bool => $processingCode->availableIn === $country, - ); - } - - private function hasInformationFor(Country $country, CouponValidityInformationArray | CouponLegalInformationArray $informations): bool - { - return array_any( - $informations->value, - fn (CouponValidityInformation | CouponLegalInformation $information): bool => $information->forLocale === $country, + CouponCountryCoverage::assert( + $this->couponProcessingCodes, + $this->couponValidityInformations, + $this->couponLegalInformations, ); } } diff --git a/tests/Unit/Application/UseCases/Coupon/Create/CreateCouponTest.php b/tests/Unit/Application/UseCases/Coupon/Create/CreateCouponTest.php index 81902ac..44b09ca 100644 --- a/tests/Unit/Application/UseCases/Coupon/Create/CreateCouponTest.php +++ b/tests/Unit/Application/UseCases/Coupon/Create/CreateCouponTest.php @@ -11,11 +11,13 @@ use DateTime; use Domain\Models\Entities\Coupon; use Domain\Models\ValueObjects\Actor\ActorId; use Domain\Models\ValueObjects\Brand\BrandCode; +use Domain\Models\ValueObjects\Coupon\CouponAvailabilityCalendar; use Domain\Models\ValueObjects\Coupon\CouponCode; use Domain\Models\ValueObjects\Coupon\CouponDiffusion; use Domain\Models\ValueObjects\Coupon\CouponId; use Domain\Models\ValueObjects\Coupon\CouponLegalInformation; use Domain\Models\ValueObjects\Coupon\CouponLegalInformationArray; +use Domain\Models\ValueObjects\Coupon\CouponMovement; use Domain\Models\ValueObjects\Coupon\CouponMovementArray; use Domain\Models\ValueObjects\Coupon\CouponName; use Domain\Models\ValueObjects\Coupon\CouponPicture; @@ -30,6 +32,7 @@ use Domain\Models\ValueObjects\Period; use Domain\Models\ValueObjects\PicturePath; use Domain\Ports\Repositories\BrandRepository; use Domain\Ports\Repositories\CouponRepository; +use Shared\DDD\UseCases\Input as BaseInput; use Shared\Enums\Country; use Shared\Enums\Currency; use Shared\ValueObjects\Money; @@ -37,41 +40,33 @@ use Shared\ValueObjects\Money; covers(CreateCoupon::class, Input::class, Output::class); describe('create coupon use case', function (): void { - $makeInput = fn (): Input => new Input( - code: new CouponCode('COU01'), - name: new CouponName('Coupon name'), - amount: new MoneyAmount(new Money(Currency::EUR, 100)), - picture: new CouponPicture( + $makeInput = fn (array $overrides = []): Input => new Input(...array_merge([ + 'code' => new CouponCode('COU01'), + 'name' => new CouponName('Coupon name'), + 'amount' => new MoneyAmount(new Money(Currency::EUR, 100)), + 'picture' => new CouponPicture( new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg'), new PicturePath('files/mobileThumbnail.jpg'), ), - diffusion: new CouponDiffusion( - new Period( - new DateTime('2026-01-01'), - new DateTime('2026-02-01'), - ), + 'diffusion' => new CouponDiffusion( + new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01')), ), - validityInformations: new CouponValidityInformationArray([ - new CouponValidityInformation( - 'Lorem ipsum dolor sit amet consectetur adipiscing elit.', - Country::FRANCE, - ), + 'validityInformations' => new CouponValidityInformationArray([ + new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), ]), - legalInformations: new CouponLegalInformationArray([ - new CouponLegalInformation( - 'Lorem ipsum dolor sit amet consectetur adipiscing elit.', - Country::FRANCE, - ), + 'legalInformations' => new CouponLegalInformationArray([ + new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), ]), - processingCode: new CouponProcessingCodeArray([ + 'processingCode' => new CouponProcessingCodeArray([ new CouponProcessingCode('5425003944130', Country::FRANCE), ]), - brandCode: new BrandCode('TST89'), - createdBy: ActorId::generate(), - products: new CouponProductArray(), - quantity: new CouponQuantity(1000), - ); + 'brandCode' => new BrandCode('TST89'), + 'createdBy' => ActorId::generate(), + 'products' => new CouponProductArray(), + 'quantity' => new CouponQuantity(1000), + 'availabilityCalendar' => null, + ], $overrides)); it('creates a coupon when the brand exists and the code is unique', function () use ($makeInput): void { // GIVEN @@ -148,4 +143,227 @@ describe('create coupon use case', function (): void { // THEN expect($action)->toThrow(\DomainException::class, 'Coupon code "COU01" already exists.'); }); + + it('initialises the stock with a first movement equal to the initial quantity', function () use ($makeInput): void { + // GIVEN + $input = $makeInput(); + + $brandRepository = \Mockery::mock(BrandRepository::class); + $brandRepository->shouldReceive('existsByCode')->once()->andReturn(true); + + $couponRepository = \Mockery::mock(CouponRepository::class); + $couponRepository->shouldReceive('existsByCode')->once()->andReturn(false); + $couponRepository->shouldReceive('save')->once()->with(\Mockery::on( + fn (Coupon $saved): bool => count($saved->movements->value) === 1 + && $saved->movements->value[0] instanceof CouponMovement + && $saved->movements->value[0]->quantity === $input->quantity->value, + )); + + $useCase = new CreateCoupon($couponRepository, $brandRepository); + + // WHEN + $result = $useCase->execute($input); + + // THEN + expect($result->movements)->toBeInstanceOf(CouponMovementArray::class) + ->and($result->movements->value)->toHaveCount(1); + }); + + it('creates a coupon with an availability calendar', function () use ($makeInput): void { + // GIVEN + $calendar = new CouponAvailabilityCalendar([ + new Period(new DateTime('2026-01-05'), new DateTime('2026-01-15')), + new Period(new DateTime('2026-01-20'), new DateTime('2026-01-25')), + ]); + $input = $makeInput(['availabilityCalendar' => $calendar]); + + $brandRepository = \Mockery::mock(BrandRepository::class); + $brandRepository->shouldReceive('existsByCode')->once()->andReturn(true); + + $couponRepository = \Mockery::mock(CouponRepository::class); + $couponRepository->shouldReceive('existsByCode')->once()->andReturn(false); + $couponRepository->shouldReceive('save')->once()->with(\Mockery::on( + fn (Coupon $saved): bool => $saved->availabilityCalendar === $calendar, + )); + + $useCase = new CreateCoupon($couponRepository, $brandRepository); + + // WHEN + $result = $useCase->execute($input); + + // THEN + expect($result->availabilityCalendar)->toBe($calendar); + }); + + it('creates a GS1 coupon with France and Benelux', function () use ($makeInput): void { + // GIVEN + $processingCode = new CouponProcessingCodeArray([ + new CouponProcessingCode('5425003944130', Country::FRANCE), + new CouponProcessingCode('5425003944147', Country::BENELUX), + ]); + $validityInformations = new CouponValidityInformationArray([ + new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), + new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::BENELUX), + ]); + $legalInformations = new CouponLegalInformationArray([ + new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), + new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::BENELUX), + ]); + $input = $makeInput([ + 'processingCode' => $processingCode, + 'validityInformations' => $validityInformations, + 'legalInformations' => $legalInformations, + ]); + + $brandRepository = \Mockery::mock(BrandRepository::class); + $brandRepository->shouldReceive('existsByCode')->once()->andReturn(true); + + $couponRepository = \Mockery::mock(CouponRepository::class); + $couponRepository->shouldReceive('existsByCode')->once()->andReturn(false); + $couponRepository->shouldReceive('save')->once()->with(\Mockery::type(Coupon::class)); + + $useCase = new CreateCoupon($couponRepository, $brandRepository); + + // WHEN + $result = $useCase->execute($input); + + // THEN + expect($result->processingCode)->toBe($processingCode) + ->and($result->validityInformations)->toBe($validityInformations) + ->and($result->legalInformations)->toBe($legalInformations); + }); + + it('rejects a coupon that targets only benelux', function () use ($makeInput): void { + // GIVEN / WHEN + $action = fn (): Input => $makeInput([ + 'processingCode' => new CouponProcessingCodeArray([ + new CouponProcessingCode('5425003944147', Country::BENELUX), + ]), + 'validityInformations' => new CouponValidityInformationArray([ + new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::BENELUX), + ]), + 'legalInformations' => new CouponLegalInformationArray([ + new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::BENELUX), + ]), + ]); + + // THEN + expect($action)->toThrow(\DomainException::class); + }); + + it('rejects a benelux processing code without benelux legal information', function () use ($makeInput): void { + // GIVEN + $input = $makeInput([ + 'processingCode' => new CouponProcessingCodeArray([ + new CouponProcessingCode('5425003944130', Country::FRANCE), + new CouponProcessingCode('5425003944147', Country::BENELUX), + ]), + 'validityInformations' => new CouponValidityInformationArray([ + new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), + new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::BENELUX), + ]), + ]); + + $brandRepository = \Mockery::mock(BrandRepository::class); + $brandRepository->shouldReceive('existsByCode')->andReturn(true); + + $couponRepository = \Mockery::mock(CouponRepository::class); + $couponRepository->shouldReceive('existsByCode')->andReturn(false); + $couponRepository->shouldNotReceive('save'); + + $useCase = new CreateCoupon($couponRepository, $brandRepository); + + // WHEN + $action = fn (): Output => $useCase->execute($input); + + // THEN + expect($action)->toThrow(\DomainException::class, 'A coupon requires legal information for BENELUX.'); + }); + + it('rejects a benelux processing code without benelux validity information', function () use ($makeInput): void { + // GIVEN + $input = $makeInput([ + 'processingCode' => new CouponProcessingCodeArray([ + new CouponProcessingCode('5425003944130', Country::FRANCE), + new CouponProcessingCode('5425003944147', Country::BENELUX), + ]), + 'legalInformations' => new CouponLegalInformationArray([ + new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), + new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::BENELUX), + ]), + ]); + + $brandRepository = \Mockery::mock(BrandRepository::class); + $brandRepository->shouldReceive('existsByCode')->andReturn(true); + + $couponRepository = \Mockery::mock(CouponRepository::class); + $couponRepository->shouldReceive('existsByCode')->andReturn(false); + $couponRepository->shouldNotReceive('save'); + + $useCase = new CreateCoupon($couponRepository, $brandRepository); + + // WHEN + $action = fn (): Output => $useCase->execute($input); + + // THEN + expect($action)->toThrow(\DomainException::class, 'A coupon requires validity information for BENELUX.'); + }); + + it('rejects two processing codes for the same country', function () use ($makeInput): void { + // GIVEN / WHEN + $action = fn (): Input => $makeInput([ + 'processingCode' => new CouponProcessingCodeArray([ + new CouponProcessingCode('5425003944130', Country::FRANCE), + new CouponProcessingCode('5425003944147', Country::FRANCE), + ]), + ]); + + // THEN + expect($action)->toThrow(\DomainException::class); + }); + + it('rejects two legal informations for the same country', function () use ($makeInput): void { + // GIVEN / WHEN + $action = fn (): Input => $makeInput([ + 'legalInformations' => new CouponLegalInformationArray([ + new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), + new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), + ]), + ]); + + // THEN + expect($action)->toThrow(\DomainException::class); + }); + + it('rejects two validity informations for the same country', function () use ($makeInput): void { + // GIVEN / WHEN + $action = fn (): Input => $makeInput([ + 'validityInformations' => new CouponValidityInformationArray([ + new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), + new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), + ]), + ]); + + // THEN + expect($action)->toThrow(\DomainException::class); + }); +}); + +describe('create coupon use case assertions', function (): void { + it('rejects an input of the wrong type', function (): void { + // GIVEN + $useCase = new CreateCoupon( + \Mockery::mock(CouponRepository::class), + \Mockery::mock(BrandRepository::class), + ); + $invalidInput = new class () implements BaseInput + { + }; + + // WHEN + $action = fn (): Output => $useCase->execute($invalidInput); + + // THEN + expect($action)->toThrow(\InvalidArgumentException::class); + }); }); diff --git a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCountryCoverageTest.php b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCountryCoverageTest.php new file mode 100644 index 0000000..d7b9b33 --- /dev/null +++ b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCountryCoverageTest.php @@ -0,0 +1,104 @@ + CouponCountryCoverage::assert( + new CouponProcessingCodeArray([$franceCode]), + new CouponValidityInformationArray([$franceValidity]), + new CouponLegalInformationArray([$franceLegal]), + ); + + // THEN + expect($action)->not->toThrow(\DomainException::class); + }); + + it('accepts a coupon fully covered for france and benelux', function () use ( + $franceCode, + $beneluxCode, + $franceValidity, + $beneluxValidity, + $franceLegal, + $beneluxLegal, + ): void { + // GIVEN / WHEN + $action = fn () => CouponCountryCoverage::assert( + new CouponProcessingCodeArray([$franceCode, $beneluxCode]), + new CouponValidityInformationArray([$franceValidity, $beneluxValidity]), + new CouponLegalInformationArray([$franceLegal, $beneluxLegal]), + ); + + // THEN + expect($action)->not->toThrow(\DomainException::class); + }); + + it('rejects a benelux processing code without benelux validity information', function () use ( + $franceCode, + $beneluxCode, + $franceValidity, + $franceLegal, + $beneluxLegal, + ): void { + // GIVEN / WHEN + $action = fn () => CouponCountryCoverage::assert( + new CouponProcessingCodeArray([$franceCode, $beneluxCode]), + new CouponValidityInformationArray([$franceValidity]), + new CouponLegalInformationArray([$franceLegal, $beneluxLegal]), + ); + + // THEN + expect($action)->toThrow(\DomainException::class, 'A coupon requires validity information for BENELUX.'); + }); + + it('rejects a benelux processing code without benelux legal information', function () use ( + $franceCode, + $beneluxCode, + $franceValidity, + $beneluxValidity, + $franceLegal, + ): void { + // GIVEN / WHEN + $action = fn () => CouponCountryCoverage::assert( + new CouponProcessingCodeArray([$franceCode, $beneluxCode]), + new CouponValidityInformationArray([$franceValidity, $beneluxValidity]), + new CouponLegalInformationArray([$franceLegal]), + ); + + // THEN + expect($action)->toThrow(\DomainException::class, 'A coupon requires legal information for BENELUX.'); + }); + + it('cannot be instantiated from the outside', function (): void { + // GIVEN + $reflection = new \ReflectionClass(CouponCountryCoverage::class); + $constructor = $reflection->getConstructor(); + + // WHEN + $constructor?->invoke($reflection->newInstanceWithoutConstructor()); + + // THEN + expect($constructor?->isPrivate())->toBeTrue(); + }); +}); diff --git a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponLegalInformationArrayTest.php b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponLegalInformationArrayTest.php index 6bcc31f..dab8bcc 100644 --- a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponLegalInformationArrayTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponLegalInformationArrayTest.php @@ -11,8 +11,8 @@ use Shared\Enums\Country; covers(CouponLegalInformationArray::class); describe('coupon legal information array', function (): void { - $franceInformation = new CouponLegalInformation('Mentions légales France.', Country::FRANCE); - $beneluxInformation = new CouponLegalInformation('Legal information Benelux.', Country::BENELUX); + $franceInformation = new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE); + $beneluxInformation = new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::BENELUX); it('creates an array containing a France legal information', function () use ($franceInformation): void { // GIVEN @@ -47,6 +47,23 @@ describe('coupon legal information array', function (): void { ); }); + it('rejects two legal informations for the same country', function () use ($franceInformation): void { + // GIVEN + $informations = [ + $franceInformation, + new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), + ]; + + // WHEN + $action = fn (): CouponLegalInformationArray => new CouponLegalInformationArray($informations); + + // THEN + expect($action)->toThrow( + \DomainException::class, + 'Only one CouponLegalInformation is allowed per country, duplicate for FRANCE.', + ); + }); + it('rejects a non legal information element', function (): void { // GIVEN / WHEN $action = fn (): CouponLegalInformationArray => new CouponLegalInformationArray(['legal']); diff --git a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponMovementArrayTest.php b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponMovementArrayTest.php index 183b1f5..c152156 100644 --- a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponMovementArrayTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponMovementArrayTest.php @@ -20,7 +20,7 @@ describe('coupon movement array', function (): void { $array = new CouponMovementArray($couponMovements); // THEN - expect($array->couponMovements)->toBe($couponMovements); + expect($array->value)->toBe($couponMovements); }); it('rejects an empty array', function (): void { diff --git a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponProcessingCodeArrayTest.php b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponProcessingCodeArrayTest.php index 6896093..14b931b 100644 --- a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponProcessingCodeArrayTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponProcessingCodeArrayTest.php @@ -47,6 +47,24 @@ describe('coupon processing code array', function (): void { ); }); + + it('rejects two legal informations for the same country', function () use ($franceCode): void { + // GIVEN + $informations = [ + $franceCode, + new CouponProcessingCode('XYZ0987654321', Country::FRANCE), + ]; + + // WHEN + $action = fn (): CouponProcessingCodeArray => new CouponProcessingCodeArray($informations); + + // THEN + expect($action)->toThrow( + \DomainException::class, + 'Only one CouponProcessingCode is allowed per country, duplicate for FRANCE.', + ); + }); + it('rejects a non processing code element', function (): void { // GIVEN / WHEN $action = fn (): CouponProcessingCodeArray => new CouponProcessingCodeArray(['ABC1234567890']); diff --git a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponValidityInformationArrayTest.php b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponValidityInformationArrayTest.php index 1a31273..c2b81fa 100644 --- a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponValidityInformationArrayTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponValidityInformationArrayTest.php @@ -11,8 +11,8 @@ use Shared\Enums\Country; covers(CouponValidityInformationArray::class); describe('coupon validity information array', function (): void { - $franceInformation = new CouponValidityInformation('Valable en France.', Country::FRANCE); - $beneluxInformation = new CouponValidityInformation('Valid in Benelux.', Country::BENELUX); + $franceInformation = new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE); + $beneluxInformation = new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::BENELUX); it('creates an array containing a France validity information', function () use ($franceInformation): void { // GIVEN @@ -47,6 +47,23 @@ describe('coupon validity information array', function (): void { ); }); + it('rejects two legal informations for the same country', function () use ($franceInformation): void { + // GIVEN + $informations = [ + $franceInformation, + new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), + ]; + + // WHEN + $action = fn (): CouponValidityInformationArray => new CouponValidityInformationArray($informations); + + // THEN + expect($action)->toThrow( + \DomainException::class, + 'Only one CouponValidityInformation is allowed per country, duplicate for FRANCE.', + ); + }); + it('rejects a non validity information element', function (): void { // GIVEN / WHEN $action = fn (): CouponValidityInformationArray => new CouponValidityInformationArray(['validity']);