code = new CouponCode('COU01'); $this->name = new CouponName('Name'); $this->amount = new MoneyAmount(new Money(Currency::EUR, 1)); $this->picture = new CouponPicture( new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg'), new PicturePath('files/inactive_thumbnail.jpg') ); $this->diffusion = new CouponDiffusion([new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'))]); $this->validity = new CouponValidity(30, new DateTime('2026-02-01')); $this->couponValidityInformation = [ new CouponValidityInformation( 'Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE ) ]; $this->couponLegalInformation = [ new CouponLegalInformation( 'Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE ) ]; $this->processingCode = new ProcessingCode('5425003944130'); $this->brandCode = new BrandCode('AAA01'); $this->createdBy = ActorId::generate(); $this->products = [new IncoCode(35784), new IncoCode(15522)]; $this->availabilityCalendar = new CouponAvailabilityCalendar([new Period(new DateTime('2026-08-25'), new DateTime('2026-10-31'))]); $this->gtin = new GS1('110101010', BarcodeFormat::EAN13); }); afterEach(function (): void { CarbonImmutable::setTestNow(); }); it('creates a coupon', function (): void { // GIVEN $id = CouponId::generate(); // WHEN $coupon = Coupon::hydrate( $id, $this->code, $this->name, $this->amount, $this->picture, $this->diffusion, $this->validity, $this->couponValidityInformation, $this->couponLegalInformation, $this->processingCode, $this->brandCode, $this->createdBy, $this->products, $this->availabilityCalendar, $this->gtin, ); // THEN expect($coupon->id())->toBe($id) ->and($coupon->code)->toBe($this->code) ->and($coupon->name)->toBe($this->name) ->and($coupon->amount)->toBe($this->amount) ->and($coupon->picture)->toBe($this->picture) ->and($coupon->diffusion)->toBe($this->diffusion) ->and($coupon->validity)->toBe($this->validity) ->and($coupon->couponValidityInformation)->toBe($this->couponValidityInformation) ->and($coupon->couponLegalInformation)->toBe($this->couponLegalInformation) ->and($coupon->processingCode)->toBe($this->processingCode) ->and($coupon->brandCode)->toBe($this->brandCode) ->and($coupon->createdBy)->toBe($this->createdBy) ->and($coupon->products)->toBe($this->products) ->and($coupon->availabilityCalendar)->toBe($this->availabilityCalendar) ->and($coupon->gtin)->toBe($this->gtin) ->and($coupon->updatedAt)->toBeNull() ->and($coupon->updatedAt())->toBeNull(); }); it('hydrates a coupon without gs1', function (): void { // GIVEN $id = CouponId::generate(); // WHEN $coupon = Coupon::hydrate( id: $id, code: $this->code, name: $this->name, amount: $this->amount, picture: $this->picture, diffusion: $this->diffusion, validity: $this->validity, couponValidityInformation: $this->couponValidityInformation, couponLegalInformation: $this->couponLegalInformation, processingCode: $this->processingCode, brandCode: $this->brandCode, createdBy: $this->createdBy, products: $this->products, availabilityCalendar: $this->availabilityCalendar ); // THEN expect($coupon->products)->toBe($this->products) ->and($coupon->availabilityCalendar)->toBe($this->availabilityCalendar) ->and($coupon->gtin)->toBeNull(); }); it('hydrates a coupon without availability calendar', function (): void { // GIVEN $id = CouponId::generate(); // WHEN $coupon = Coupon::hydrate( id: $id, code: $this->code, name: $this->name, amount: $this->amount, picture: $this->picture, diffusion: $this->diffusion, validity: $this->validity, couponValidityInformation: $this->couponValidityInformation, couponLegalInformation: $this->couponLegalInformation, processingCode: $this->processingCode, brandCode: $this->brandCode, createdBy: $this->createdBy, products: $this->products, gtin: $this->gtin ); // THEN expect($coupon->availabilityCalendar)->toBeNull() ->and($coupon->products)->toBe($this->products) ->and($coupon->gtin)->toBe($this->gtin); }); it('hydrates a coupon without products', function (): void { // GIVEN $id = CouponId::generate(); // WHEN $coupon = Coupon::hydrate( id: $id, code: $this->code, name: $this->name, amount: $this->amount, picture: $this->picture, diffusion: $this->diffusion, validity: $this->validity, couponValidityInformation: $this->couponValidityInformation, couponLegalInformation: $this->couponLegalInformation, processingCode: $this->processingCode, brandCode: $this->brandCode, createdBy: $this->createdBy, availabilityCalendar: $this->availabilityCalendar, gtin: $this->gtin ); // THEN expect($coupon->products)->toBe([]) ->and($coupon->availabilityCalendar)->toBe($this->availabilityCalendar) ->and($coupon->gtin)->toBe($this->gtin); }); it('hydrates a coupon with only required fields', function (): void { // GIVEN $id = CouponId::generate(); // WHEN $coupon = Coupon::hydrate( id: $id, code: $this->code, name: $this->name, amount: $this->amount, picture: $this->picture, diffusion: $this->diffusion, validity: $this->validity, couponValidityInformation: $this->couponValidityInformation, couponLegalInformation: $this->couponLegalInformation, processingCode: $this->processingCode, brandCode: $this->brandCode, createdBy: $this->createdBy, ); // THEN expect($coupon->id())->toBe($id) ->and($coupon->products)->toBe([]) ->and($coupon->availabilityCalendar)->toBeNull() ->and($coupon->gtin)->toBeNull() ->and($coupon->updatedAt)->toBeNull() ->and($coupon->updatedAt())->toBeNull(); }); it('accepts empty validity and legal information lists', function (): void { // GIVEN $id = CouponId::generate(); // WHEN $coupon = Coupon::hydrate( $id, $this->code, $this->name, $this->amount, $this->picture, $this->diffusion, $this->validity, [], [], $this->processingCode, $this->brandCode, $this->createdBy, $this->products, $this->availabilityCalendar, $this->gtin, ); // THEN expect($coupon->couponValidityInformation)->toBe([]) ->and($coupon->couponLegalInformation)->toBe([]); }); it('throws when validity information contains an invalid item', function (): void { // GIVEN $id = CouponId::generate(); // WHEN / THEN expect(fn () => Coupon::hydrate( $id, $this->code, $this->name, $this->amount, $this->picture, $this->diffusion, $this->validity, ['Not a CouponValidityInformation'], $this->couponLegalInformation, $this->processingCode, $this->brandCode, $this->createdBy, $this->products, $this->availabilityCalendar, $this->gtin, ))->toThrow(\DomainException::class, 'Coupon validity information must be CouponValidityInformation instances.'); }); it('throws when legal information contains an invalid item', function (): void { // GIVEN $id = CouponId::generate(); // WHEN / THEN expect(fn () => Coupon::hydrate( $id, $this->code, $this->name, $this->amount, $this->picture, $this->diffusion, $this->validity, $this->couponValidityInformation, ['Not a CouponLegalInformation'], $this->processingCode, $this->brandCode, $this->createdBy, $this->products, $this->availabilityCalendar, $this->gtin, ))->toThrow(\DomainException::class, 'Coupon legal information must be CouponLegalInformation instances.'); }); it('hydrates a coupon with an explicit updatedAt date', function (): void { // GIVEN $id = CouponId::generate(); $updatedAt = new \DateTimeImmutable('2026-09-22 10:00:00', new \DateTimeZone('UTC')); // WHEN $coupon = Coupon::hydrate( $id, $this->code, $this->name, $this->amount, $this->picture, $this->diffusion, $this->validity, $this->couponValidityInformation, $this->couponLegalInformation, $this->processingCode, $this->brandCode, $this->createdBy, $this->products, $this->availabilityCalendar, $this->gtin, $updatedAt ); // THEN expect($coupon->updatedAt)->toBe($updatedAt) ->and($coupon->updatedAt())->toBe($updatedAt); }); it('creates a new coupon with a freshly generated id', function (): void { // GIVEN / WHEN $coupon = Coupon::create( $this->code, $this->name, $this->amount, $this->picture, $this->diffusion, $this->validity, $this->couponValidityInformation, $this->couponLegalInformation, $this->processingCode, $this->brandCode, $this->createdBy, $this->products, $this->availabilityCalendar, $this->gtin, ); // THEN expect($coupon->id())->toBeInstanceOf(CouponId::class) ->and($coupon->code)->toBe($this->code) ->and($coupon->name)->toBe($this->name) ->and($coupon->amount)->toBe($this->amount) ->and($coupon->picture)->toBe($this->picture) ->and($coupon->diffusion)->toBe($this->diffusion) ->and($coupon->validity)->toBe($this->validity) ->and($coupon->couponValidityInformation)->toBe($this->couponValidityInformation) ->and($coupon->couponLegalInformation)->toBe($this->couponLegalInformation) ->and($coupon->processingCode)->toBe($this->processingCode) ->and($coupon->brandCode)->toBe($this->brandCode) ->and($coupon->createdBy)->toBe($this->createdBy) ->and($coupon->products)->toBe($this->products) ->and($coupon->availabilityCalendar)->toBe($this->availabilityCalendar) ->and($coupon->gtin)->toBe($this->gtin) ->and($coupon->updatedAt)->toBeNull() ->and($coupon->updatedAt())->toBeNull(); }); it('generates a different id for each created coupon', function (): void { // GIVEN / WHEN $firstCoupon = Coupon::create( $this->code, $this->name, $this->amount, $this->picture, $this->diffusion, $this->validity, $this->couponValidityInformation, $this->couponLegalInformation, $this->processingCode, $this->brandCode, $this->createdBy, $this->products, $this->availabilityCalendar, $this->gtin, ); $secondCoupon = Coupon::create( $this->code, $this->name, $this->amount, $this->picture, $this->diffusion, $this->validity, $this->couponValidityInformation, $this->couponLegalInformation, $this->processingCode, $this->brandCode, $this->createdBy, $this->products, $this->availabilityCalendar, $this->gtin, ); // THEN expect($firstCoupon->id()->equals($secondCoupon->id()))->toBeFalse(); }); it('returns the creation date extracted from the id', function (): void { // GIVEN $expected = new \DateTimeImmutable('2024-06-15 12:34:56.789000', new \DateTimeZone('UTC')); $id = new CouponId(Uuid::uuid7($expected)); $coupon = Coupon::hydrate( $id, new CouponCode('COU01'), new CouponName('Name'), new MoneyAmount(new Money(Currency::EUR, 1)), new CouponPicture( new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg'), new PicturePath('files/inactive_thumbnail.jpg') ), new CouponDiffusion([new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'))]), new CouponValidity(30, new DateTime('2026-02-01')), [new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE)], [new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE)], new ProcessingCode('5425003944130'), new BrandCode('AAA01'), ActorId::generate(), [new IncoCode(35784), new IncoCode(15522)], new CouponAvailabilityCalendar([new Period(new DateTime('2026-08-25'), new DateTime('2026-10-31'))]), new GS1('110101010', BarcodeFormat::EAN13), ); // WHEN $result = $coupon->createdAt(); // THEN expect($result->format('Y-m-d H:i:s.v'))->toBe($expected->format('Y-m-d H:i:s.v')); }); });