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')), ), '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), ]), 'processingCode' => new ProcessingCodeArray([ new ProcessingCode('5425003944130', Country::FRANCE), ]), 'brandCode' => new BrandCode('TST89'), 'createdBy' => ActorId::generate(), 'products' => new CouponProductArray(), 'movements' => new CouponMovementArray([ new CouponMovement(100), ]), 'availabilityCalendar' => null, ], $overrides)); it('creates a coupon when the brand exists and the code is unique', function () use ($makeInput): void { // GIVEN $input = $makeInput(); $brandRepository = \Mockery::mock(BrandRepository::class); $brandRepository->shouldReceive('existsByCode')->once()->with($input->brandCode)->andReturn(true); $couponRepository = \Mockery::mock(CouponRepository::class); $couponRepository->shouldReceive('existsByCode')->once()->with($input->code)->andReturn(false); $couponRepository->shouldReceive('save')->once()->with(\Mockery::type(Coupon::class)); $useCase = new CreateCoupon($couponRepository, $brandRepository); // WHEN $result = $useCase->execute($input); // THEN expect($result)->toBeInstanceOf(CouponCreated::class) ->and($result->id)->toBeInstanceOf(CouponId::class) ->and($result->code)->toEqual($input->code) ->and($result->name)->toEqual($input->name) ->and($result->amount)->toEqual($input->amount) ->and($result->picture)->toEqual($input->picture) ->and($result->diffusion)->toEqual($input->diffusion) ->and($result->validityInformations)->toEqual($input->validityInformations) ->and($result->legalInformations)->toEqual($input->legalInformations) ->and($result->processingCode)->toEqual($input->processingCode) ->and($result->brandCode)->toEqual($input->brandCode) ->and($result->createdBy)->toEqual($input->createdBy) ->and($result->products)->toEqual($input->products) ->and($result->movements)->toEqual($input->movements) ->and($result->availabilityCalendar)->toBeNull(); }); it('throws when the brand does not exist', function () use ($makeInput): void { // GIVEN $input = $makeInput(); $brandRepository = \Mockery::mock(BrandRepository::class); $brandRepository->shouldReceive('existsByCode')->once()->with($input->brandCode)->andReturn(false); $couponRepository = \Mockery::mock(CouponRepository::class); $couponRepository->shouldNotReceive('existsByCode'); $couponRepository->shouldNotReceive('save'); $useCase = new CreateCoupon($couponRepository, $brandRepository); // WHEN $result = $useCase->execute($input); // THEN expect($result)->toBeInstanceOf(BrandNotFound::class); }); it('throws when the coupon code already exists', function () use ($makeInput): void { // GIVEN $input = $makeInput(); $brandRepository = \Mockery::mock(BrandRepository::class); $brandRepository->shouldReceive('existsByCode')->once()->with($input->brandCode)->andReturn(true); $couponRepository = \Mockery::mock(CouponRepository::class); $couponRepository->shouldReceive('existsByCode')->once()->with($input->code)->andReturn(true); $couponRepository->shouldNotReceive('save'); $useCase = new CreateCoupon($couponRepository, $brandRepository); // WHEN $result = $useCase->execute($input); // THEN expect($result)->toBeInstanceOf(CouponCodeAlreadyExists::class); }); 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 ProcessingCodeArray([ new ProcessingCode('5425003944130', Country::FRANCE), new ProcessingCode('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 ProcessingCodeArray([ new ProcessingCode('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 ProcessingCodeArray([ new ProcessingCode('5425003944130', Country::FRANCE), new ProcessingCode('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 () => $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 ProcessingCodeArray([ new ProcessingCode('5425003944130', Country::FRANCE), new ProcessingCode('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 () => $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 ProcessingCodeArray([ new ProcessingCode('5425003944130', Country::FRANCE), new ProcessingCode('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 () => $useCase->execute($invalidInput); // THEN expect($action)->toThrow(\InvalidArgumentException::class); }); });