ldc-mesbr-app/tests/Unit/Domain/Models/Entities/CampaignTest.php
2026-10-26 17:10:25 +01:00

470 lines
16 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\Entities;
use Carbon\CarbonImmutable;
use DateTime;
use DateTimeInterface;
use Domain\Models\Entities\Campaign;
use Domain\Models\Entities\Coupon;
use Domain\Models\ValueObjects\Actor\ActorId;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Campaign\CampaignCode;
use Domain\Models\ValueObjects\Campaign\CampaignCoupon;
use Domain\Models\ValueObjects\Campaign\CampaignCouponArray;
use Domain\Models\ValueObjects\Campaign\CampaignId;
use Domain\Models\ValueObjects\Campaign\CampaignName;
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;
use Domain\Models\ValueObjects\Coupon\CouponProductArray;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformationArray;
use Domain\Models\ValueObjects\IncoCode;
use Domain\Models\ValueObjects\MoneyAmount;
use Domain\Models\ValueObjects\Period;
use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\ProcessingCodeArray;
use Domain\Ports\Repositories\CouponRepository;
use Ramsey\Uuid\Uuid;
use Shared\Enums\Country;
use Shared\Enums\Currency;
use Shared\Helpers\DatesHelper;
use Shared\ValueObjects\Money;
covers(Campaign::class);
describe('campaign', function (): void {
$name = new CampaignName('Name');
$printAllAtOnce = \boolval(1);
$coupons = new CampaignCouponArray([
new CampaignCoupon(
new CouponCode('AAA01'),
new PrintRules(maxPerCustomer: 3)
)
]);
$brandCode = new BrandCode('AAA01');
$actorId = ActorId::generate();
$couponCode = new CouponCode('COU01');
$couponName = new CouponName('Coupon name');
$couponDiffusionStart = new DateTime()->modify('-1 month');
$couponDiffusionEnd = new DateTime()->modify('+1 month');
$makeCoupon = fn (array $overrides = []): Coupon => Coupon::create(
code: $couponCode,
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(
new CouponCode('AAA01'),
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('now', new \DateTimeZone('UTC'));
$campaign = Campaign::create(
$name,
$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, 'Coupons must be CampaignCoupon instances.');
});
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('throws when unpublish an already unpublished campaign', function () use (
$code,
$name,
$printAllAtOnce,
$coupons,
$brandCode,
$actorId
): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC');
$id = CampaignId::generate();
$oldPublicationStatus = false;
$campaign = Campaign::hydrate(
$id,
$code,
$name,
$oldPublicationStatus,
$printAllAtOnce,
$coupons,
$brandCode,
$actorId
);
// WHEN
$action = fn() => $campaign = $campaign->unpublish();
// THEN
expect($action)->toThrow(\DomainException::class, 'Campaign is already unpublished.');
});
it('publishes a campaign and sets updatedAt timestamp', function () use (
$code,
$name,
$printAllAtOnce,
$coupons,
$brandCode,
$actorId,
$makeCoupon
): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC');
$now = DatesHelper::now();
$id = CampaignId::generate();
$oldPublicationStatus = false;
$newPublicationStatus = true;
$campaign = Campaign::hydrate(
$id,
$code,
$name,
$oldPublicationStatus,
$printAllAtOnce,
$coupons,
$brandCode,
$actorId
);
// Coupons valides
$printableCoupon = $makeCoupon([
'diffusionStart' => $now->modify('-1 month')->toDateTime(),
'diffusionEnd' => $now->modify('+1 month')->toDateTime(),
]);
// WHEN
$campaign = $campaign->publish([$printableCoupon]);
// 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('throws when publish an already published campaign', function () use ($code, $name, $printAllAtOnce, $coupons, $brandCode, $actorId): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC');
$id = CampaignId::generate();
$oldPublicationStatus = true;
$campaign = Campaign::hydrate(
$id,
$code,
$name,
$oldPublicationStatus,
$printAllAtOnce,
$coupons,
$brandCode,
$actorId
);
// WHEN
$action = fn() => $campaign = $campaign->publish([]);
// THEN
expect($action)->toThrow(\DomainException::class, 'Campaign is already published.');
});
it('throws when publish a campaign with no active coupons', function () use (
$code,
$name,
$printAllAtOnce,
$brandCode,
$actorId,
$makeCoupon
): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC');
$now = DatesHelper::now();
$id = CampaignId::generate();
$oldPublicationStatus = false;
$printableCoupon = $makeCoupon([
'diffusionStart' => $now->modify('+1 month')->toDateTime(),
'diffusionEnd' => $now->modify('+2 month')->toDateTime(),
]);
$coupons = new CampaignCouponArray([
new CampaignCoupon($printableCoupon->code)
]);
$campaign = Campaign::hydrate(
$id,
$code,
$name,
$oldPublicationStatus,
$printAllAtOnce,
$coupons,
$brandCode,
$actorId
);
// WHEN
$action = fn() => $campaign = $campaign->publish([$printableCoupon]);
// THEN
expect($action)->toThrow(\DomainException::class, 'At least 1 available coupon should be provided to activate a campaign.');
});
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);
});
});