diff --git a/src/Application/UseCases/CampaignRequest/List/Input.php b/src/Application/UseCases/CampaignRequest/List/Input.php index 9b2802c..5683492 100644 --- a/src/Application/UseCases/CampaignRequest/List/Input.php +++ b/src/Application/UseCases/CampaignRequest/List/Input.php @@ -13,7 +13,7 @@ final readonly class Input implements UseCaseInput { public function __construct( public Pagination $pagination, - public ?CampaignRequestSortCriteria $sort = null, + public CampaignRequestSortCriteria $sort, public ?CampaignRequestFilters $filters = null, ) { } diff --git a/src/Domain/Models/ValueObjects/MoneyAmount.php b/src/Domain/Models/ValueObjects/MoneyAmount.php index cb0b753..31f307d 100644 --- a/src/Domain/Models/ValueObjects/MoneyAmount.php +++ b/src/Domain/Models/ValueObjects/MoneyAmount.php @@ -12,9 +12,18 @@ use Shared\ValueObjects\Money; */ final readonly class MoneyAmount implements \Stringable, Equatable { + private const int MIN_FACE_VALUE_IN_CENTS = 1; + private const int MAX_FACE_VALUE_IN_CENTS = 300; + public function __construct( public Money $value, ) { + if ($this->value->value < self::MIN_FACE_VALUE_IN_CENTS || $this->value->value > self::MAX_FACE_VALUE_IN_CENTS) { + throw new \DomainException(sprintf( + 'Money amount must be between 0.01€ and 3.00€, %s given.', + $this->value, + )); + } } #[\Override] diff --git a/src/Domain/Models/ValueObjects/RequestedCoupon/RequestedCoupon.php b/src/Domain/Models/ValueObjects/RequestedCoupon/RequestedCoupon.php index 8fc0c3c..c5bfbab 100644 --- a/src/Domain/Models/ValueObjects/RequestedCoupon/RequestedCoupon.php +++ b/src/Domain/Models/ValueObjects/RequestedCoupon/RequestedCoupon.php @@ -17,9 +17,6 @@ use Shared\Enums\Country; final readonly class RequestedCoupon { - private const int MIN_FACE_VALUE_IN_CENTS = 1; - private const int MAX_FACE_VALUE_IN_CENTS = 300; - /** * @param array $couponValidityInformation * @param array $products @@ -36,21 +33,12 @@ final readonly class RequestedCoupon public ?PrintRules $printRules = null, public ?GS1 $gs1 = null, ) { - $money = $this->moneyAmount->value; - foreach ($this->couponValidityInformation as $validityInformation) { if (!$validityInformation instanceof CouponValidityInformation) { throw new \DomainException('Coupon validity information must be CouponValidityInformation instances.'); } } - if ($money->value < self::MIN_FACE_VALUE_IN_CENTS || $money->value > self::MAX_FACE_VALUE_IN_CENTS) { - throw new \DomainException(sprintf( - 'Coupon face value must be between 0.01€ and 3.00€, %s given.', - $money, - )); - } - if (!$this->hasValidityInformationFor(Country::FRANCE)) { throw new \DomainException('A coupon requires validity information for France.'); } diff --git a/src/Domain/Ports/Repositories/CampaignRequestRepository.php b/src/Domain/Ports/Repositories/CampaignRequestRepository.php index 909202a..4b9c521 100644 --- a/src/Domain/Ports/Repositories/CampaignRequestRepository.php +++ b/src/Domain/Ports/Repositories/CampaignRequestRepository.php @@ -25,7 +25,7 @@ interface CampaignRequestRepository extends Repository */ public function findAll( Pagination $pagination, - ?CampaignRequestSortCriteria $sortCriteria = null, + CampaignRequestSortCriteria $sortCriteria, ?CampaignRequestFilters $filters = null, ): Paginated; } diff --git a/tests/Unit/Application/UseCases/CampaignRequest/List/ListCampaignsRequestTest.php b/tests/Unit/Application/UseCases/CampaignRequest/List/ListCampaignsRequestTest.php index aa717b2..cfb1914 100644 --- a/tests/Unit/Application/UseCases/CampaignRequest/List/ListCampaignsRequestTest.php +++ b/tests/Unit/Application/UseCases/CampaignRequest/List/ListCampaignsRequestTest.php @@ -71,11 +71,18 @@ describe('list campaigns request use case', function (): void { new PrintRules(maxPerCustomer: 3), new GS1('01234567890128', BarcodeFormat::EAN13) ); + + $this->pagination = Pagination::firstPage(); + + $this->sort = new CampaignRequestSortCriteria( + clauses: [ + new CampaignRequestSortClause(field: CampaignRequestSortField::CreatedAt, direction: \SortDirection::Descending), + ] + ); }); it('maps repository campaigns request to list items, preserving pagination and sort', function (): void { // GIVEN - $pagination = Pagination::firstPage(); $campaignRequest1 = CampaignRequest::hydrate( CampaignRequestId::generate(), new BrandCode('AAA01'), @@ -94,7 +101,7 @@ describe('list campaigns request use case', function (): void { $this->couponB, CampaignRequestStatus::CREATED, ); - $paginated = new Paginated($pagination, [$campaignRequest1, $campaignRequest2], 2); + $paginated = new Paginated($this->pagination, [$campaignRequest1, $campaignRequest2], 2); $sort = new CampaignRequestSortCriteria( clauses: [ @@ -103,12 +110,12 @@ describe('list campaigns request use case', function (): void { ] ); $filters = new CampaignRequestFilters($campaignRequest2->brandCode, CampaignRequestStatus::CREATED); - $input = new Input(pagination: $pagination, sort: $sort, filters: $filters); + $input = new Input(pagination: $this->pagination, sort: $sort, filters: $filters); $repository = \Mockery::mock(CampaignRequestRepository::class); $repository->shouldReceive('findAll') ->once() - ->with($pagination, CampaignRequestSortCriteria::class, CampaignRequestFilters::class) + ->with($this->pagination, CampaignRequestSortCriteria::class, CampaignRequestFilters::class) ->andReturn($paginated); $useCase = new ListCampaignsRequest($repository); @@ -128,12 +135,11 @@ describe('list campaigns request use case', function (): void { ->and($items[0]->campaignType)->toBe($campaignRequest1->campaignType) ->and($items[0]->status)->toBe($campaignRequest1->status) ->and($result->campaignsRequest->getTotal())->toBe(2) - ->and($result->campaignsRequest->getPagination())->toBe($pagination); + ->and($result->campaignsRequest->getPagination())->toBe($this->pagination); }); - it('lists campaigns request with filters but without sort', function (): void { + it('lists campaigns request with filters', function (): void { // GIVEN - $pagination = Pagination::firstPage(); $campaignRequest = CampaignRequest::hydrate( CampaignRequestId::generate(), new BrandCode('AAA01'), @@ -143,15 +149,15 @@ describe('list campaigns request use case', function (): void { $this->couponA, CampaignRequestStatus::CREATED, ); - $paginated = new Paginated($pagination, [$campaignRequest], 1); + $paginated = new Paginated($this->pagination, [$campaignRequest], 1); $filters = new CampaignRequestFilters($campaignRequest->brandCode, CampaignRequestStatus::CREATED); - $input = new Input(pagination: $pagination, filters: $filters); + $input = new Input(pagination: $this->pagination, sort: $this->sort, filters: $filters); $repository = \Mockery::mock(CampaignRequestRepository::class); $repository->shouldReceive('findAll') ->once() - ->with($pagination, null, $filters) + ->with($this->pagination, $this->sort, $filters) ->andReturn($paginated); $useCase = new ListCampaignsRequest($repository); @@ -167,12 +173,11 @@ describe('list campaigns request use case', function (): void { ->and($items[0]->name)->toBe($campaignRequest->name) ->and($items[0]->status)->toBe($campaignRequest->status) ->and($result->campaignsRequest->getTotal())->toBe(1) - ->and($result->campaignsRequest->getPagination())->toBe($pagination); + ->and($result->campaignsRequest->getPagination())->toBe($this->pagination); }); it('lists campaigns request with sort but without filters', function (): void { // GIVEN - $pagination = Pagination::firstPage(); $campaignRequest1 = CampaignRequest::hydrate( CampaignRequestId::generate(), new BrandCode('AAA01'), @@ -191,19 +196,19 @@ describe('list campaigns request use case', function (): void { $this->couponB, CampaignRequestStatus::CREATED, ); - $paginated = new Paginated($pagination, [$campaignRequest2, $campaignRequest1], 2); + $paginated = new Paginated($this->pagination, [$campaignRequest2, $campaignRequest1], 2); $sort = new CampaignRequestSortCriteria( clauses: [ new CampaignRequestSortClause(field: CampaignRequestSortField::BrandCode, direction: \SortDirection::Descending), ] ); - $input = new Input(pagination: $pagination, sort: $sort); + $input = new Input(pagination: $this->pagination, sort: $sort); $repository = \Mockery::mock(CampaignRequestRepository::class); $repository->shouldReceive('findAll') ->once() - ->with($pagination, $sort, null) + ->with($this->pagination, $sort, null) ->andReturn($paginated); $useCase = new ListCampaignsRequest($repository); @@ -217,19 +222,18 @@ describe('list campaigns request use case', function (): void { ->and($items[0]->brandCode)->toBe($campaignRequest2->brandCode) ->and($items[1]->brandCode)->toBe($campaignRequest1->brandCode) ->and($result->campaignsRequest->getTotal())->toBe(2) - ->and($result->campaignsRequest->getPagination())->toBe($pagination); + ->and($result->campaignsRequest->getPagination())->toBe($this->pagination); }); it('returns an empty list without error when there are no campaigns request', function (): void { // GIVEN - $pagination = Pagination::firstPage(); - $paginated = new Paginated($pagination, [], 0); - $input = new Input(pagination: $pagination); + $paginated = new Paginated($this->pagination, [], 0); + $input = new Input(pagination: $this->pagination, sort: $this->sort); $repository = \Mockery::mock(CampaignRequestRepository::class); $repository->shouldReceive('findAll') ->once() - ->with($pagination, null, null) + ->with($this->pagination, $this->sort, null) ->andReturn($paginated); $useCase = new ListCampaignsRequest($repository); diff --git a/tests/Unit/Domain/Models/ValueObjects/MoneyAmountTest.php b/tests/Unit/Domain/Models/ValueObjects/MoneyAmountTest.php index dbd4190..84ea7e2 100644 --- a/tests/Unit/Domain/Models/ValueObjects/MoneyAmountTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/MoneyAmountTest.php @@ -12,8 +12,11 @@ covers(MoneyAmount::class); describe('money amount', function (): void { beforeEach(function (): void { - $this->money = new Money(Currency::EUR, 300); - $this->secondMoney = new Money(Currency::EUR, 500); + $this->money = new Money(Currency::EUR, 300); + $this->secondMoney = new Money(Currency::EUR, 200); + $this->minimumMoney = new Money(Currency::EUR, 1); + $this->zeroMoney = new Money(Currency::EUR, 0); + $this->aboveLimitMoney = new Money(Currency::EUR, 301); }); it('wraps a Money value', function (): void { @@ -54,4 +57,32 @@ describe('money amount', function (): void { // THEN expect((string) $amount)->toBe((string) $amount->value); }); + + it('accepts the minimum amount of 0.01€', function (): void { + // GIVEN / WHEN + $amount = new MoneyAmount($this->minimumMoney); + + // THEN + expect($amount->value)->toBe($this->minimumMoney); + }); + + it('accepts the maximum amount of 3.00€', function (): void { + // GIVEN / WHEN + $amount = new MoneyAmount($this->money); + + // THEN + expect($amount->value)->toBe($this->money); + }); + + it('throws when the amount is zero', function (): void { + // GIVEN / WHEN / THEN + expect(fn () => new MoneyAmount($this->zeroMoney)) + ->toThrow(\DomainException::class, 'Money amount must be between 0.01€ and 3.00€'); + }); + + it('throws when the amount is above 3.00€', function (): void { + // GIVEN / WHEN / THEN + expect(fn () => new MoneyAmount($this->aboveLimitMoney)) + ->toThrow(\DomainException::class, 'Money amount must be between 0.01€ and 3.00€'); + }); }); diff --git a/tests/Unit/Domain/Models/ValueObjects/RequestedCoupon/RequestedCouponTest.php b/tests/Unit/Domain/Models/ValueObjects/RequestedCoupon/RequestedCouponTest.php index 111a145..f55da32 100644 --- a/tests/Unit/Domain/Models/ValueObjects/RequestedCoupon/RequestedCouponTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/RequestedCoupon/RequestedCouponTest.php @@ -188,70 +188,6 @@ describe('requested coupon', function (): void { ))->toThrow(\DomainException::class, 'Coupon validity information must be CouponValidityInformation instances.'); }); - it('accepts the minimum face value of 0.01€', function (): void { - // GIVEN - $moneyAmount = new MoneyAmount(new Money(Currency::EUR, 1)); - - // WHEN - $coupon = new RequestedCoupon( - name: $this->name, - quantity: $this->quantity, - diffusion: $this->diffusion, - moneyAmount: $moneyAmount, - processingCode: $this->processingCode, - couponValidity: $this->couponValidity, - couponValidityInformation: $this->couponValidityInformation, - ); - - // THEN - expect($coupon->moneyAmount)->toBe($moneyAmount); - }); - - it('accepts the maximum face value of 3.00€', function (): void { - // GIVEN - $moneyAmount = new MoneyAmount(new Money(Currency::EUR, 300)); - - // WHEN - $coupon = new RequestedCoupon( - name: $this->name, - quantity: $this->quantity, - diffusion: $this->diffusion, - moneyAmount: $moneyAmount, - processingCode: $this->processingCode, - couponValidity: $this->couponValidity, - couponValidityInformation: $this->couponValidityInformation, - ); - - // THEN - expect($coupon->moneyAmount)->toBe($moneyAmount); - }); - - it('throws when the face value is zero', function (): void { - // GIVEN / WHEN / THEN - expect(fn () => new RequestedCoupon( - name: $this->name, - quantity: $this->quantity, - diffusion: $this->diffusion, - moneyAmount: new MoneyAmount(new Money(Currency::EUR, 0)), - processingCode: $this->processingCode, - couponValidity: $this->couponValidity, - couponValidityInformation: $this->couponValidityInformation, - ))->toThrow(\DomainException::class, 'Coupon face value must be between 0.01€ and 3.00€'); - }); - - it('throws when the face value is above 3.00€', function (): void { - // GIVEN / WHEN / THEN - expect(fn () => new RequestedCoupon( - name: $this->name, - quantity: $this->quantity, - diffusion: $this->diffusion, - moneyAmount: new MoneyAmount(new Money(Currency::EUR, 301)), - processingCode: $this->processingCode, - couponValidity: $this->couponValidity, - couponValidityInformation: $this->couponValidityInformation, - ))->toThrow(\DomainException::class, 'Coupon face value must be between 0.01€ and 3.00€'); - }); - it('throws when a gs1 coupon has no benelux validity information', function (): void { // GIVEN / WHEN / THEN expect(fn () => new RequestedCoupon(