modify('-1 month'); $couponDiffusionEnd = new \DateTime()->modify('+1 month'); $makeCoupon = fn (array $overrides = []): Coupon => Coupon::create( name: $couponName, amount: new MoneyAmount(new Money(Currency::EUR, 100)), picture: new CouponPicture( new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg'), new PicturePath('files/mobile_thumbnail.jpg'), ), diffusion: new CouponDiffusion( new Period( $overrides['diffusionStart'] ?? $couponDiffusionStart, $overrides['diffusionEnd'] ?? $couponDiffusionEnd, ), ), 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: $brandCode, createdBy: ActorId::generate(), products: new CouponProductArray([ new IncoCode(35784), new IncoCode(15522), ]), movements: new CouponMovementArray([ new CouponMovement(1000) ]), availabilityCalendar: $overrides['availabilityCalendar'] ?? null, ); afterEach(function (): void { CarbonImmutable::setTestNow(); }); it('creates a campaign', function (): void { // GIVEN $name = new CampaignName('Name'); $coupons = new CampaignCouponArray([ new CampaignCoupon( CouponCode::generate(), new PrintRules(maxPerCustomer: 3) ) ]); $brandCode = new BrandCode('AAA01'); $actorId = ActorId::generate(); // WHEN $campaign = Campaign::create( $name, true, $coupons, $brandCode, $actorId ); // THEN expect($campaign->id())->toBeInstanceOf(CampaignId::class) ->and($campaign->code)->toBeInstanceOf(CampaignCode::class) ->and($campaign->name)->toBe($name) ->and($campaign->publication)->toBeFalse() ->and($campaign->printAllAtOnce)->toBeTrue() ->and($campaign->coupons)->toBe($coupons) ->and($campaign->brandCode)->toBe($brandCode) ->and($campaign->actorId)->toBe($actorId) ->and($campaign->createdAt())->toBeInstanceOf(DateTimeInterface::class) ->and($campaign->updatedAt())->toBeNull(); }); it('generates a different id for each created campaign', function () use ( $name, $printAllAtOnce, $coupons, $brandCode, $actorId, ): void { // GIVEN / WHEN $firstCampaign = Campaign::create( $name, $printAllAtOnce, $coupons, $brandCode, $actorId ); $secondCampaign = Campaign::create( $name, $printAllAtOnce, $coupons, $brandCode, $actorId ); // THEN expect($firstCampaign->id()->equals($secondCampaign->id()))->toBeFalse(); }); it('returns the creation date extracted from the id', function () use ( $name, $printAllAtOnce, $coupons, $brandCode, $actorId, ): void { // GIVEN $expected = new \DateTimeImmutable('2024-06-15 12:34:56.789000', new \DateTimeZone('UTC')); $id = new CampaignId(Uuid::uuid7($expected)); $campaign = Campaign::hydrate( $id, CampaignCode::generate(), $name, false, $printAllAtOnce, $coupons, $brandCode, $actorId ); // WHEN $result = $campaign->createdAt(); // THEN expect($result->format('Y-m-d H:i:s.v'))->toBe($expected->format('Y-m-d H:i:s.v')); }); it('rejects a campaign with a non-coupon element', function () use ($code, $name, $publication, $printAllAtOnce, $brandCode, $actorId): void { // GIVEN $id = CampaignId::generate(); // WHEN / THEN expect(fn (): Campaign => Campaign::hydrate( $id, $code, $name, $publication, $printAllAtOnce, new CampaignCouponArray(['PC1']), $brandCode, $actorId ))->toThrow( \DomainException::class, 'CampaignCouponArray only accepts CampaignCoupon.' ); }); it('unpublish a campaign and sets updatedAt timestamp', function () use ($code, $name, $printAllAtOnce, $coupons, $brandCode, $actorId): void { // GIVEN CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC'); $id = CampaignId::generate(); $oldPublicationStatus = true; $newPublicationStatus = false; $campaign = Campaign::hydrate( $id, $code, $name, $oldPublicationStatus, $printAllAtOnce, $coupons, $brandCode, $actorId ); // WHEN $campaign = $campaign->unpublish(); // THEN expect($campaign->id())->toBe($id) ->and($campaign->code)->toBe($code) ->and($campaign->name)->toBe($name) ->and($campaign->publication)->toBe($newPublicationStatus) ->and($campaign->printAllAtOnce)->toBe($printAllAtOnce) ->and($campaign->coupons)->toBe($coupons) ->and($campaign->createdAt())->toBeInstanceOf(DateTimeInterface::class) ->and($campaign->updatedAt())->toBeInstanceOf(DateTimeInterface::class) ->and($campaign->updatedAt()->format('Y-m-d H:i:s'))->toBe('2026-06-15 16:45:00'); }); it('publishes a campaign and sets updatedAt timestamp', function () use ( $code, $name, $printAllAtOnce, $coupons, $brandCode, $actorId, ): void { // GIVEN CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC'); $id = CampaignId::generate(); $oldPublicationStatus = false; $newPublicationStatus = true; $campaign = Campaign::hydrate( $id, $code, $name, $oldPublicationStatus, $printAllAtOnce, $coupons, $brandCode, $actorId ); // WHEN $campaign = $campaign->publish(); // THEN expect($campaign->id())->toBe($id) ->and($campaign->code)->toBe($code) ->and($campaign->name)->toBe($name) ->and($campaign->publication)->toBe($newPublicationStatus) ->and($campaign->printAllAtOnce)->toBe($printAllAtOnce) ->and($campaign->coupons)->toBe($coupons) ->and($campaign->createdAt())->toBeInstanceOf(DateTimeInterface::class) ->and($campaign->updatedAt())->toBeInstanceOf(DateTimeInterface::class) ->and($campaign->updatedAt()->format('Y-m-d H:i:s'))->toBe('2026-06-15 16:45:00'); }); it('unpublishes a campaign when there are no available coupons in it', function () use ( $code, $name, $printAllAtOnce, $coupons, $brandCode, $actorId, $makeCoupon ): void { // GIVEN CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC'); $id = CampaignId::generate(); $oldPublicationStatus = true; $newPublicationStatus = false; $campaign = Campaign::hydrate( $id, $code, $name, $oldPublicationStatus, $printAllAtOnce, $coupons, $brandCode, $actorId ); $expired = $makeCoupon([ 'diffusionStart' => new \DateTime('2026-05-01'), 'diffusionEnd' => new \DateTime('2026-06-30'), ]); // WHEN CarbonImmutable::setTestNow('2026-08-16 00:00:00 UTC'); $campaign = $campaign->unpublishWhenNoAvailableCoupons([$expired]); // THEN expect($campaign->id())->toBe($id) ->and($campaign->code)->toBe($code) ->and($campaign->name)->toBe($name) ->and($campaign->publication)->toBe($newPublicationStatus) ->and($campaign->printAllAtOnce)->toBe($printAllAtOnce) ->and($campaign->coupons)->toBe($coupons) ->and($campaign->createdAt())->toBeInstanceOf(DateTimeInterface::class) ->and($campaign->updatedAt())->toBeInstanceOf(DateTimeInterface::class); }); it('keeps a campaign published when there are available coupons in it', function () use ( $code, $name, $printAllAtOnce, $coupons, $brandCode, $actorId, $makeCoupon ): void { // GIVEN CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC'); $id = CampaignId::generate(); $oldPublicationStatus = true; $newPublicationStatus = true; $campaign = Campaign::hydrate( $id, $code, $name, $oldPublicationStatus, $printAllAtOnce, $coupons, $brandCode, $actorId ); $notExpired = $makeCoupon([ 'diffusionStart' => new \DateTime('2026-06-01'), 'diffusionEnd' => new \DateTime('2026-08-30'), ]); // WHEN CarbonImmutable::setTestNow('2026-08-16 00:00:00 UTC'); $campaign = $campaign->unpublishWhenNoAvailableCoupons([$notExpired]); // THEN expect($campaign->id())->toBe($id) ->and($campaign->code)->toBe($code) ->and($campaign->name)->toBe($name) ->and($campaign->publication)->toBe($newPublicationStatus) ->and($campaign->printAllAtOnce)->toBe($printAllAtOnce) ->and($campaign->coupons)->toBe($coupons) ->and($campaign->createdAt())->toBeInstanceOf(DateTimeInterface::class) ->and($campaign->updatedAt())->toBeInstanceOf(DateTimeInterface::class); }); it('gets the codes of the associated coupons', function (): void { // GIVEN $couponCode1 = CouponCode::generate(); $couponCode2 = CouponCode::generate(); $expectedCoupons = [$couponCode1->value, $couponCode2->value]; $campaign = CampaignFactory::hydrated(couponCodes: [$couponCode1, $couponCode2]); // WHEN $coupons = array_map(static fn (CouponCode $c): string => $c->value, $campaign->couponCodes()); // THEN expect($coupons)->toBe($expectedCoupons); }); it('removes a coupon from a campaign', function () use ( $code, $name, $printAllAtOnce, $coupons, $brandCode, $actorId, $makeCoupon ): void { // GIVEN CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC'); $id = CampaignId::generate(); $coupon = $makeCoupon(); $expectedCoupons = $coupons; $coupons = $coupons->addCoupon(new CampaignCoupon($coupon->code)); $campaign = Campaign::hydrate( $id, $code, $name, true, $printAllAtOnce, $coupons, $brandCode, $actorId ); // WHEN CarbonImmutable::setTestNow('2026-08-16 00:00:00 UTC'); $campaign = $campaign->removeCoupon($coupon->code); // THEN expect($campaign->id())->toBe($id) ->and($campaign->coupons)->toEqual($expectedCoupons) ->and($campaign->createdAt())->toBeInstanceOf(DateTimeInterface::class) ->and($campaign->updatedAt())->toBeInstanceOf(DateTimeInterface::class); }); it('adds a coupon to a campaign', function () use ( $code, $name, $printAllAtOnce, $coupons, $brandCode, $actorId, ): void { // GIVEN CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC'); $id = CampaignId::generate(); $couponCode = CouponCode::generate(); $campaign = Campaign::hydrate( $id, $code, $name, true, $printAllAtOnce, $coupons, $brandCode, $actorId ); $expectedCoupons = $coupons->addCoupon(new CampaignCoupon($couponCode)); // WHEN $updatedCampaign = $campaign->addCoupon($couponCode); // THEN expect($updatedCampaign->id())->toBe($id) ->and($updatedCampaign->coupons)->toEqual($expectedCoupons) ->and($updatedCampaign->createdAt())->toBeInstanceOf(DateTimeInterface::class) ->and($updatedCampaign->updatedAt())->toBeInstanceOf(DateTimeInterface::class); }); });