From 491e349959fcaf7194fb353b7bb76d4cdbe687f4 Mon Sep 17 00:00:00 2001 From: Noah Lancien Date: Thu, 29 Oct 2026 18:10:17 +0100 Subject: [PATCH] feat: :sparkles: Transform CampaignRequest into Campaign + Coupon - #38 - US0.3 --- .../CampaignRequest/Transform/Input.php | 16 ++ .../CampaignRequestAlreadyTranformed.php | 11 ++ .../Outputs/CampaignRequestNotFound.php | 11 ++ .../Outputs/CampaignRequestTransformed.php | 22 +++ .../Transform/Outputs/CouponAlreadyExists.php | 11 ++ .../Transform/TransformCampaignRequest.php | 122 ++++++++++++++ .../UseCases/Coupon/Create/CreateCoupon.php | 7 +- .../UseCases/Coupon/Create/Input.php | 2 - .../Models/Entities/CampaignRequest.php | 15 ++ src/Domain/Models/Entities/Coupon.php | 3 +- .../Models/ValueObjects/Coupon/CouponCode.php | 24 +-- .../Ports/Repositories/CouponRepository.php | 7 +- tests/Fixtures/Campaign/CampaignFactory.php | 4 +- .../CampaignRequestFactory.php | 135 ++++++++++++++++ tests/Fixtures/Coupon/CouponFactory.php | 50 +++--- .../Campaign/Create/CreateCampaignTest.php | 4 +- .../Campaign/Find/FindCampaignTest.php | 12 +- .../Update/AddCouponToCampaignTest.php | 29 ++-- .../Update/AutoUnpublishCampaignTest.php | 23 +-- .../Campaign/Update/PublishCampaignTest.php | 14 +- .../Update/RemoveCouponToCampaignTest.php | 10 +- .../TransformCampaignRequestTest.php | 150 ++++++++++++++++++ .../Coupon/Create/CreateCouponTest.php | 54 +++++-- .../UseCases/Coupon/Find/FindCouponTest.php | 3 +- .../UseCases/Coupon/List/ListCouponTest.php | 4 +- .../Coupon/Update/UpdateCouponTest.php | 6 +- .../Create/CreatePrintableCouponTest.php | 120 +++----------- .../List/ListPrintableCouponTest.php | 11 +- .../Domain/Models/Entities/CampaignTest.php | 24 +-- .../Domain/Models/Entities/CouponTest.php | 10 +- .../Models/Entities/PrintableCouponTest.php | 2 +- .../Unit/Domain/Models/Entities/StockTest.php | 2 +- .../Campaign/CampaignCouponArrayTest.php | 38 +++-- .../Campaign/CampaignCouponTest.php | 6 +- .../ValueObjects/Coupon/CouponCodeTest.php | 92 ++++------- .../PrintableCoupon/PrintedCouponTest.php | 2 +- .../PrintableCoupon/UnprintedCouponTest.php | 2 +- .../CampaignAvailabilityCheckerTest.php | 70 ++++---- 38 files changed, 787 insertions(+), 341 deletions(-) create mode 100644 src/Application/UseCases/CampaignRequest/Transform/Input.php create mode 100644 src/Application/UseCases/CampaignRequest/Transform/Outputs/CampaignRequestAlreadyTranformed.php create mode 100644 src/Application/UseCases/CampaignRequest/Transform/Outputs/CampaignRequestNotFound.php create mode 100644 src/Application/UseCases/CampaignRequest/Transform/Outputs/CampaignRequestTransformed.php create mode 100644 src/Application/UseCases/CampaignRequest/Transform/Outputs/CouponAlreadyExists.php create mode 100644 src/Application/UseCases/CampaignRequest/Transform/TransformCampaignRequest.php create mode 100644 tests/Fixtures/CampaignRequest/CampaignRequestFactory.php create mode 100644 tests/Unit/Application/UseCases/CampaignRequest/Transform/TransformCampaignRequestTest.php diff --git a/src/Application/UseCases/CampaignRequest/Transform/Input.php b/src/Application/UseCases/CampaignRequest/Transform/Input.php new file mode 100644 index 0000000..1905229 --- /dev/null +++ b/src/Application/UseCases/CampaignRequest/Transform/Input.php @@ -0,0 +1,16 @@ + */ +final readonly class TransformCampaignRequest implements UseCase, Transactional +{ + public function __construct( + private CampaignRequestRepository $repository, + private CampaignRepository $campaignRepository, + private CouponRepository $couponRepository, + ) { + } + + #[\Override] + public function execute(BaseInput $input): Output + { + Assert::isInstanceOf($input, Input::class); + + $campaignRequest = $this->repository->findById($input->id); + + if (! $campaignRequest instanceof CampaignRequest) { + return new CampaignRequestNotFound(); + } + + if ($campaignRequest->status !== CampaignRequestStatus::CREATED) { + return new CampaignRequestAlreadyTranformed(); + } + + // TODO: get the real ActorID + $actorId = ActorId::generate(); + + $createdCoupon = self::createCouponFromRequest($campaignRequest, $actorId); + + + $campaignCoupon = new CampaignCoupon($createdCoupon->code, $campaignRequest->requestedCoupon->printRules); + + $campaignCreated = self::createCampaignFromRequest($campaignRequest, $campaignCoupon, $actorId); + + $campaignRequest = $campaignRequest->updateStatusWhenTransformed(); + $this->repository->save($campaignRequest); + + return new CampaignRequestTransformed( + id: $campaignRequest->id(), + status: $campaignRequest->status, + campaign: $campaignCreated, + coupon: $createdCoupon, + ); + } + + private function createCouponFromRequest(CampaignRequest $campaignRequest, ActorId $actorId): Coupon | CouponAlreadyExists + { + $requestedCoupon = $campaignRequest->requestedCoupon; + + foreach ($requestedCoupon->couponProcessingCodes->toArray() as $processingCode) { + if ($this->couponRepository->existsByProcessingCode($processingCode)) { + return new CouponAlreadyExists(); + } + } + + $coupon = Coupon::create( + name: $requestedCoupon->couponName, + amount: $requestedCoupon->moneyAmount, + picture: $requestedCoupon->couponPicture, + diffusion: new CouponDiffusion($requestedCoupon->diffusion), + validityInformations: $requestedCoupon->couponValidityInformations, + legalInformations: $requestedCoupon->couponLegalInformations, + processingCode: $requestedCoupon->couponProcessingCodes, + brandCode: $campaignRequest->brandCode, + createdBy: $actorId, + products: $requestedCoupon->couponProducts, + movements: new CouponMovementArray([ + new CouponMovement($requestedCoupon->quantity->value) + ]), + ); + $this->couponRepository->save($coupon); + + return $coupon; + } + + private function createCampaignFromRequest(CampaignRequest $campaignRequest, CampaignCoupon $campaignCoupon, ActorId $actorId): Campaign + { + $isGame = $campaignRequest->campaignType === CampaignRequestType::GAME; + $campaign = Campaign::create( + name: $campaignRequest->name, + printAllAtOnce: $isGame, + coupons: new CampaignCouponArray([$campaignCoupon]), + brandCode: $campaignRequest->brandCode, + actorId: $actorId + ); + $this->campaignRepository->save($campaign); + + return $campaign; + } +} diff --git a/src/Application/UseCases/Coupon/Create/CreateCoupon.php b/src/Application/UseCases/Coupon/Create/CreateCoupon.php index acf5689..50ece3b 100644 --- a/src/Application/UseCases/Coupon/Create/CreateCoupon.php +++ b/src/Application/UseCases/Coupon/Create/CreateCoupon.php @@ -34,12 +34,13 @@ final readonly class CreateCoupon implements UseCase, Transactional return new BrandNotFound(); } - if ($this->couponRepository->existsByCode($input->code)) { - return new CouponCodeAlreadyExists(); + foreach ($input->processingCode->toArray() as $processingCode) { + if ($this->couponRepository->existsByProcessingCode($processingCode)) { + return new CouponCodeAlreadyExists(); + } } $coupon = Coupon::create( - code: $input->code, name: $input->name, amount: $input->amount, picture: $input->picture, diff --git a/src/Application/UseCases/Coupon/Create/Input.php b/src/Application/UseCases/Coupon/Create/Input.php index 9fc1bb4..f788988 100644 --- a/src/Application/UseCases/Coupon/Create/Input.php +++ b/src/Application/UseCases/Coupon/Create/Input.php @@ -7,7 +7,6 @@ namespace Application\UseCases\Coupon\Create; use Domain\Models\ValueObjects\Actor\ActorId; use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Coupon\CouponAvailabilityCalendar; -use Domain\Models\ValueObjects\Coupon\CouponCode; use Domain\Models\ValueObjects\Coupon\CouponDiffusion; use Domain\Models\ValueObjects\Coupon\CouponLegalInformationArray; use Domain\Models\ValueObjects\Coupon\CouponMovementArray; @@ -22,7 +21,6 @@ use Shared\DDD\UseCases\Input as UseCaseInput; final readonly class Input implements UseCaseInput { public function __construct( - public CouponCode $code, public CouponName $name, public MoneyAmount $amount, public CouponPicture $picture, diff --git a/src/Domain/Models/Entities/CampaignRequest.php b/src/Domain/Models/Entities/CampaignRequest.php index 3fad464..7277d98 100644 --- a/src/Domain/Models/Entities/CampaignRequest.php +++ b/src/Domain/Models/Entities/CampaignRequest.php @@ -124,4 +124,19 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent return $this->rejectionReason; } + + public function updateStatusWhenTransformed(): self + { + return new self( + id: $this->id, + brandCode: $this->brandCode, + applicant: $this->applicant, + campaignType: $this->campaignType, + name: $this->name, + requestedCoupon: $this->requestedCoupon, + status: CampaignRequestStatus::TRANSFORMED, + rejectionReason: null, + updatedAt: DatesHelper::now(), + ); + } } diff --git a/src/Domain/Models/Entities/Coupon.php b/src/Domain/Models/Entities/Coupon.php index c4181a8..a63d416 100644 --- a/src/Domain/Models/Entities/Coupon.php +++ b/src/Domain/Models/Entities/Coupon.php @@ -75,7 +75,6 @@ final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier } public static function create( - CouponCode $code, CouponName $name, MoneyAmount $amount, CouponPicture $picture, @@ -91,7 +90,7 @@ final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier ): Coupon { return new self( id: CouponId::generate(), - code: $code, + code: CouponCode::generate(), name: $name, amount: $amount, picture: $picture, diff --git a/src/Domain/Models/ValueObjects/Coupon/CouponCode.php b/src/Domain/Models/ValueObjects/Coupon/CouponCode.php index 09a09c4..2d1e7dc 100644 --- a/src/Domain/Models/ValueObjects/Coupon/CouponCode.php +++ b/src/Domain/Models/ValueObjects/Coupon/CouponCode.php @@ -4,23 +4,13 @@ declare(strict_types=1); namespace Domain\Models\ValueObjects\Coupon; -final readonly class CouponCode +use Ramsey\Uuid\Uuid; +use Shared\DDD\Models\Uuidv7EntityId; + +final readonly class CouponCode extends Uuidv7EntityId { - private const string REGEX = '/^[A-Z]{3}\d{2}$/'; - - public function __construct( - public string $value, - ) { - if (! preg_match(self::REGEX, $value)) { - throw new \DomainException( - "Invalid code '{$value}' does not match pattern " . self::REGEX, - ); - } - - if ($value[3] === $value[4]) { - throw new \DomainException( - "Invalid code '{$value}' has the same digit repeated", - ); - } + public static function generate(): self + { + return new self(Uuid::uuid7()); } } diff --git a/src/Domain/Ports/Repositories/CouponRepository.php b/src/Domain/Ports/Repositories/CouponRepository.php index 1fa1986..899ee04 100644 --- a/src/Domain/Ports/Repositories/CouponRepository.php +++ b/src/Domain/Ports/Repositories/CouponRepository.php @@ -8,6 +8,7 @@ use Domain\Models\Entities\Coupon; use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Coupon\CouponCode; use Domain\Models\ValueObjects\Coupon\CouponSortCriteria; +use Domain\Models\ValueObjects\ProcessingCode; use Shared\DDD\Ports\Repository; use Shared\ValueObjects\Pagination\Paginated; use Shared\ValueObjects\Pagination\Pagination; @@ -21,6 +22,10 @@ interface CouponRepository extends Repository public function findByCode(CouponCode $code): ?Coupon; + public function existsByProcessingCode(ProcessingCode $code): bool; + + public function findByProcessingCode(ProcessingCode $code): bool; + /** * @param array $code * @@ -37,5 +42,5 @@ interface CouponRepository extends Repository ?BrandCode $brandCode = null, ): Paginated; - public function save(Coupon $brand): void; + public function save(Coupon $coupon): void; } diff --git a/tests/Fixtures/Campaign/CampaignFactory.php b/tests/Fixtures/Campaign/CampaignFactory.php index b9f4800..3042242 100644 --- a/tests/Fixtures/Campaign/CampaignFactory.php +++ b/tests/Fixtures/Campaign/CampaignFactory.php @@ -94,10 +94,10 @@ final class CampaignFactory return new CampaignCouponArray(array_map( static fn (string | CouponCode $code): CampaignCoupon => new CampaignCoupon( - $code instanceof CouponCode ? $code : new CouponCode($code), + $code instanceof CouponCode ? $code : CouponCode::generate(), $printRules, ), - $couponCodes ?? ['COU01'], + $couponCodes ?? [CouponCode::generate()], )); } } diff --git a/tests/Fixtures/CampaignRequest/CampaignRequestFactory.php b/tests/Fixtures/CampaignRequest/CampaignRequestFactory.php new file mode 100644 index 0000000..872a640 --- /dev/null +++ b/tests/Fixtures/CampaignRequest/CampaignRequestFactory.php @@ -0,0 +1,135 @@ +reject($reason ?? self::rejectionReason()); + } + + /** Demande transformée en campagne, obtenue par la vraie transition métier. */ + public static function transformed(): CampaignRequest + { + return self::make()->updateStatusWhenTransformed(); + } + + public static function applicant( + string $firstname = 'Jane', + string $lastname = 'Doe', + string $email = 'jane.doe@example.com', + ): Applicant { + return new Applicant($firstname, $lastname, new Email($email)); // Email : À VÉRIFIER + } + + public static function rejectionReason(string $reason = 'Not eligible'): RejectionReason + { + return new RejectionReason($reason); + } + + public static function requestedCoupon( + string $couponName = 'Coupon name', + int $quantity = 1000, + ?PrintRules $printRules = null, + ): RequestedCoupon { + return new RequestedCoupon( + couponName: new CouponName($couponName), + quantity: new RequestedCouponQuantity($quantity), // À VÉRIFIER + diffusion: new Period(new \DateTime()->modify('+1 month'), new \DateTime()->modify('+2 months')), + moneyAmount: new MoneyAmount(new Money(Currency::EUR, 100)), + couponPicture: new CouponPicture( + new PicturePath('files/product.jpg'), + new PicturePath('files/thumbnail.jpg'), + new PicturePath('files/mobile_thumbnail.jpg'), + ), + couponProcessingCodes: new ProcessingCodeArray([ + new ProcessingCode('5425003944130', Country::FRANCE), + ]), + couponValidityInformations: new CouponValidityInformationArray([ + new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur.', Country::FRANCE), + ]), + couponLegalInformations: new CouponLegalInformationArray([ + new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur.', Country::FRANCE), + ]), + couponProducts: new CouponProductArray([new IncoCode(35784)]), + printRules: $printRules, + ); + } +} diff --git a/tests/Fixtures/Coupon/CouponFactory.php b/tests/Fixtures/Coupon/CouponFactory.php index d2c8560..17a8da9 100644 --- a/tests/Fixtures/Coupon/CouponFactory.php +++ b/tests/Fixtures/Coupon/CouponFactory.php @@ -10,6 +10,7 @@ use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Coupon\CouponAvailabilityCalendar; 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; @@ -32,65 +33,74 @@ use Shared\ValueObjects\Money; final class CouponFactory { public static function make( - string | CouponCode $code = 'COU01', ?int $amount = 100, ?\DateTime $diffusionStart = null, ?\DateTime $diffusionEnd = null, ?CouponAvailabilityCalendar $availabilityCalendar = null, string $brandCode = 'AAA01', + ?CouponCode $code = null, ): Coupon { - return Coupon::create( - code: $code instanceof CouponCode ? $code : new CouponCode($code), - name: new CouponName('Coupon name'), - amount: new MoneyAmount(new Money(Currency::EUR, $amount)), - picture: new CouponPicture( + $arguments = [ + 'name' => new CouponName('Coupon name'), + 'amount' => new MoneyAmount(new Money(Currency::EUR, $amount)), + 'picture' => new CouponPicture( new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg'), new PicturePath('files/mobile_thumbnail.jpg'), ), - diffusion: new CouponDiffusion( + 'diffusion' => new CouponDiffusion( new Period( start: $diffusionStart ?? new \DateTime()->modify('-1 month'), end: $diffusionEnd ?? new \DateTime()->modify('+1 month'), ), ), - validityInformations: new CouponValidityInformationArray([ + '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([ + '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) ]), - processingCode: new ProcessingCodeArray([ + 'processingCode' => new ProcessingCodeArray([ new ProcessingCode('5425003944130', Country::FRANCE), new ProcessingCode('4006381333931', Country::BENELUX), ]), - brandCode: new BrandCode($brandCode), - createdBy: ActorId::generate(), - products: new CouponProductArray([new IncoCode(35784), new IncoCode(15522)]), - movements: new CouponMovementArray([new CouponMovement(1000)]), - availabilityCalendar: $availabilityCalendar, - ); + 'brandCode' => new BrandCode($brandCode), + 'createdBy' => ActorId::generate(), + 'products' => new CouponProductArray([new IncoCode(35784), new IncoCode(15522)]), + 'movements' => new CouponMovementArray([new CouponMovement(1000)]), + 'availabilityCalendar' => $availabilityCalendar, + ]; + + if ($code instanceof CouponCode) { + return Coupon::hydrate(...[ + 'id' => CouponId::generate(), + 'code' => $code, + ...$arguments, + ]); + } + + return Coupon::create(...$arguments); } /** Coupon dont la diffusion est terminée. */ - public static function expired(string | CouponCode $code = 'COU01'): Coupon + public static function expired(?CouponCode $code = null): Coupon { return self::make( - code: $code, diffusionStart: new \DateTime()->modify('-2 months'), diffusionEnd: new \DateTime()->modify('-1 day'), + code: $code, ); } /** Coupon dont la diffusion n'a pas commencé. */ - public static function upcoming(string | CouponCode $code = 'COU01'): Coupon + public static function upcoming(?CouponCode $code = null): Coupon { return self::make( - code: $code, diffusionStart: new \DateTime()->modify('+1 day'), diffusionEnd: new \DateTime()->modify('+2 months'), + code: $code, ); } } diff --git a/tests/Unit/Application/UseCases/Campaign/Create/CreateCampaignTest.php b/tests/Unit/Application/UseCases/Campaign/Create/CreateCampaignTest.php index f3168d4..ff3341c 100644 --- a/tests/Unit/Application/UseCases/Campaign/Create/CreateCampaignTest.php +++ b/tests/Unit/Application/UseCases/Campaign/Create/CreateCampaignTest.php @@ -27,8 +27,8 @@ describe('create campaign use case', function (): void { 'brandCode' => new BrandCode('AAA01'), 'printAllAtOnce' => false, 'coupons' => new CampaignCouponArray([ - new CampaignCoupon(new CouponCode('COU01'), new PrintRules(maxPerCustomer: 1)), - new CampaignCoupon(new CouponCode('COU02'), new PrintRules(cooldownDays: 30)), + new CampaignCoupon(CouponCode::generate(), new PrintRules(maxPerCustomer: 1)), + new CampaignCoupon(CouponCode::generate(), new PrintRules(cooldownDays: 30)), ]), 'createdBy' => ActorId::generate(), ], $overrides)); diff --git a/tests/Unit/Application/UseCases/Campaign/Find/FindCampaignTest.php b/tests/Unit/Application/UseCases/Campaign/Find/FindCampaignTest.php index dcb2fee..3b277b1 100644 --- a/tests/Unit/Application/UseCases/Campaign/Find/FindCampaignTest.php +++ b/tests/Unit/Application/UseCases/Campaign/Find/FindCampaignTest.php @@ -10,6 +10,7 @@ use Application\UseCases\Campaign\Find\Outputs\CampaignFound; use Application\UseCases\Campaign\Outputs\CampaignNotFound; use Domain\Models\Entities\Campaign; use Domain\Models\ValueObjects\Campaign\CampaignCode; +use Domain\Models\ValueObjects\Coupon\CouponCode; use Domain\Ports\Repositories\CampaignRepository; use Domain\Models\ValueObjects\PrintRules; use Tests\Fixtures\Campaign\CampaignFactory; @@ -17,15 +18,16 @@ use Tests\Fixtures\Campaign\CampaignFactory; covers(FindCampaign::class, Input::class, CampaignFound::class, CampaignNotFound::class); describe('find campaign use case', function (): void { - $makeCampaign = static fn (array $couponCodes = ['COU01']): Campaign => CampaignFactory::hydrated( + $couponCode = CouponCode::generate(); + $makeCampaign = static fn (?array $couponCodes = null): Campaign => CampaignFactory::hydrated( published: true, name: 'Campaign A', printAllAtOnce: false, - couponCodes: $couponCodes, + couponCodes: $couponCodes ?? [$couponCode], printRules: new PrintRules(maxPerCustomer: 1), ); - it('finds a campaign by its code with paginated coupons', function () use ($makeCampaign): void { + it('finds a campaign by its code with coupons array', function () use ($makeCampaign, $couponCode): void { // GIVEN $campaign = $makeCampaign(); $repository = \Mockery::mock(CampaignRepository::class); @@ -44,14 +46,14 @@ describe('find campaign use case', function (): void { ->and($result->publication)->toBe($campaign->publication) ->and($result->printAllAtOnce)->toBe($campaign->printAllAtOnce) ->and($result->coupons)->toBe($campaign->coupons) - ->and($result->coupons->toArray()[0]->couponCode->value)->toBe('COU01') + ->and($result->coupons->toArray()[0]->couponCode->value)->toBe($couponCode->value) ->and($result->createdAt)->toEqual($campaign->createdAt()) ->and($result->updatedAt)->toEqual($campaign->updatedAt()); }); it('returns CampaignNotFound when the code is not found', function (): void { // GIVEN - $code = CampaignCode::generate(); + $code = CampaignCode::generate(); $repository = \Mockery::mock(CampaignRepository::class); $repository->shouldReceive('findByCode')->once()->with($code)->andReturn(null); diff --git a/tests/Unit/Application/UseCases/Campaign/Update/AddCouponToCampaignTest.php b/tests/Unit/Application/UseCases/Campaign/Update/AddCouponToCampaignTest.php index e7f8fbc..6f11206 100644 --- a/tests/Unit/Application/UseCases/Campaign/Update/AddCouponToCampaignTest.php +++ b/tests/Unit/Application/UseCases/Campaign/Update/AddCouponToCampaignTest.php @@ -11,7 +11,6 @@ use Application\UseCases\Campaign\Update\Outputs\CampaignUpdated; use Application\UseCases\Campaign\Update\Outputs\CouponAlreadyInCampaign; use Application\UseCases\Campaign\Update\Outputs\CouponNotActive; use Application\UseCases\Campaign\Update\Outputs\CouponNotFound; -use DateTime; use Domain\Models\Entities\Campaign; use Domain\Models\ValueObjects\Actor\ActorId; use Domain\Models\ValueObjects\Brand\BrandCode; @@ -24,6 +23,7 @@ use Domain\Models\ValueObjects\Coupon\CouponCode; use Domain\Models\ValueObjects\PrintRules; use Domain\Ports\Repositories\CampaignRepository; use Domain\Ports\Repositories\CouponRepository; +use Shared\Helpers\DatesHelper; use Tests\Fixtures\Campaign\CampaignFactory; use Tests\Fixtures\Coupon\CouponFactory; @@ -38,9 +38,9 @@ covers( ); describe('add coupons to campaign use case', function (): void { - $coupon1 = CouponFactory::make(code: 'COU01'); - $coupon2 = CouponFactory::make(code: 'COU02'); - $coupon3 = CouponFactory::make(code: 'COU03'); + $coupon1 = CouponFactory::make(); + $coupon2 = CouponFactory::make(); + $coupon3 = CouponFactory::make(); $campaignCoupon1 = new CampaignCoupon($coupon1->code, new PrintRules(maxPerCustomer: 1)); $campaignCoupon2 = new CampaignCoupon($coupon2->code, new PrintRules(maxPerCustomer: 1)); $existingCampaign = static fn (CampaignCode $code, bool $basePublicationStatus): Campaign => Campaign::hydrate( @@ -114,11 +114,11 @@ describe('add coupons to campaign use case', function (): void { it('returns a specific error when the coupon is not printable', function (): void { // WHEN - $campaign = CampaignFactory::hydrated(couponCodes: ['COU01']); - $coupon = CouponFactory::make( - code: 'COU02', - diffusionStart: new DateTime('2026-01-01'), - diffusionEnd: new DateTime('2026-01-31') + $baseCoupon = CouponFactory::make(); + $campaign = CampaignFactory::hydrated(couponCodes: [$baseCoupon->code]); + $couponToAdd = CouponFactory::make( + diffusionStart: DatesHelper::now()->subYear()->subMonth()->toDateTime(), + diffusionEnd: DatesHelper::now()->subYear()->toDateTime() ); $repository = \Mockery::mock(CampaignRepository::class); @@ -126,9 +126,9 @@ describe('add coupons to campaign use case', function (): void { $repository->shouldNotReceive('save'); $couponRepository = \Mockery::mock(CouponRepository::class); - $couponRepository->shouldReceive('findByCode')->once()->andReturn($coupon); + $couponRepository->shouldReceive('findByCode')->once()->andReturn($couponToAdd); - $input = new AddCouponToCampaignInput(code: $campaign->code, couponCode: $coupon->code, printRules: null); + $input = new AddCouponToCampaignInput(code: $campaign->code, couponCode: $couponToAdd->code, printRules: null); $useCase = new AddCouponToCampaign($repository, $couponRepository); @@ -160,16 +160,17 @@ describe('add coupons to campaign use case', function (): void { it('returns a specific error when the coupon is not found', function (): void { // GIVEN - $campaign = CampaignFactory::hydrated(); + $campaign = CampaignFactory::hydrated(); + $couponCode = CouponCode::generate(); $repository = \Mockery::mock(CampaignRepository::class); $repository->shouldReceive('findByCode')->once()->andReturn($campaign); $repository->shouldNotReceive('save'); $couponRepository = \Mockery::mock(CouponRepository::class); - $couponRepository->shouldReceive('findByCode')->once()->andReturn(null); + $couponRepository->shouldReceive('findByCode')->once()->with($couponCode)->andReturn(null); - $input = new AddCouponToCampaignInput(code: $campaign->code, couponCode: new CouponCode('ZZZ01'), printRules: null); + $input = new AddCouponToCampaignInput(code: $campaign->code, couponCode: $couponCode, printRules: null); $useCase = new AddCouponToCampaign($repository, $couponRepository); diff --git a/tests/Unit/Application/UseCases/Campaign/Update/AutoUnpublishCampaignTest.php b/tests/Unit/Application/UseCases/Campaign/Update/AutoUnpublishCampaignTest.php index 9369ace..e2a3e43 100644 --- a/tests/Unit/Application/UseCases/Campaign/Update/AutoUnpublishCampaignTest.php +++ b/tests/Unit/Application/UseCases/Campaign/Update/AutoUnpublishCampaignTest.php @@ -11,6 +11,7 @@ use Application\UseCases\Campaign\Update\Outputs\CampaignStatusUpdated; use Application\UseCases\Campaign\Update\Outputs\CampaignUnpublicationSkipped; use Domain\Models\Entities\Campaign; use Domain\Models\ValueObjects\Campaign\CampaignCode; +use Domain\Models\ValueObjects\Coupon\CouponCode; use Domain\Ports\Repositories\CampaignRepository; use Domain\Ports\Repositories\CouponRepository; use Domain\Services\Campaign\CampaignAvailabilityChecker; @@ -68,14 +69,15 @@ describe('auto unpublish campaign use case', function (): void { it('skips a published campaign that still has an active coupon', function (): void { // GIVEN - $campaign = CampaignFactory::hydrated(published: true, couponCodes: ['COU01']); + $couponCode = CouponCode::generate(); + $campaign = CampaignFactory::hydrated(published: true, couponCodes: [$couponCode]); $repository = \Mockery::mock(CampaignRepository::class); $repository->shouldReceive('findByCode')->once()->with($campaign->code)->andReturn($campaign); $repository->shouldNotReceive('save'); $couponRepository = \Mockery::mock(CouponRepository::class); - $couponRepository->shouldReceive('findByCodes')->once()->andReturn([CouponFactory::make('COU01')]); + $couponRepository->shouldReceive('findByCodes')->once()->andReturn([CouponFactory::make()]); $useCase = new AutoUnpublishCampaign($repository, new CampaignAvailabilityChecker($couponRepository)); @@ -88,7 +90,7 @@ describe('auto unpublish campaign use case', function (): void { it('skips a published campaign with one active and one expired coupon', function (): void { // GIVEN - $campaign = CampaignFactory::hydrated(published: true, couponCodes: ['COU01', 'COU02']); + $campaign = CampaignFactory::hydrated(published: true, couponCodes: [CouponCode::generate(), CouponCode::generate()]); $repository = \Mockery::mock(CampaignRepository::class); $repository->shouldReceive('findByCode')->once()->with($campaign->code)->andReturn($campaign); @@ -96,8 +98,8 @@ describe('auto unpublish campaign use case', function (): void { $couponRepository = \Mockery::mock(CouponRepository::class); $couponRepository->shouldReceive('findByCodes')->once()->andReturn([ - CouponFactory::expired('COU01'), - CouponFactory::make('COU02'), + CouponFactory::expired(), + CouponFactory::make(), ]); $useCase = new AutoUnpublishCampaign($repository, new CampaignAvailabilityChecker($couponRepository)); @@ -111,7 +113,9 @@ describe('auto unpublish campaign use case', function (): void { it('unpublishes a published campaign when every coupon is not available', function (): void { // GIVEN - $campaign = CampaignFactory::hydrated(published: true, couponCodes: ['COU01', 'COU02']); + $couponCode1 = CouponCode::generate(); + $couponCode2 = CouponCode::generate(); + $campaign = CampaignFactory::hydrated(published: true, couponCodes: [$couponCode1->value, $couponCode2->value]); $repository = \Mockery::mock(CampaignRepository::class); $repository->shouldReceive('findByCode')->once()->with($campaign->code)->andReturn($campaign); @@ -123,8 +127,8 @@ describe('auto unpublish campaign use case', function (): void { $couponRepository = \Mockery::mock(CouponRepository::class); $couponRepository->shouldReceive('findByCodes')->once()->andReturn([ - CouponFactory::expired('COU01'), - CouponFactory::upcoming('COU02'), + CouponFactory::expired(), + CouponFactory::upcoming(), ]); $useCase = new AutoUnpublishCampaign($repository, new CampaignAvailabilityChecker($couponRepository)); @@ -143,7 +147,8 @@ describe('auto unpublish campaign use case', function (): void { it('unpublishes a published campaign when no coupon is found', function (): void { // GIVEN - $campaign = CampaignFactory::hydrated(published: true, couponCodes: ['COU01']); + $couponCode = CouponCode::generate(); + $campaign = CampaignFactory::hydrated(published: true, couponCodes: [$couponCode]); $repository = \Mockery::mock(CampaignRepository::class); $repository->shouldReceive('findByCode')->once()->with($campaign->code)->andReturn($campaign); diff --git a/tests/Unit/Application/UseCases/Campaign/Update/PublishCampaignTest.php b/tests/Unit/Application/UseCases/Campaign/Update/PublishCampaignTest.php index 96a5e6a..cd1d970 100644 --- a/tests/Unit/Application/UseCases/Campaign/Update/PublishCampaignTest.php +++ b/tests/Unit/Application/UseCases/Campaign/Update/PublishCampaignTest.php @@ -27,8 +27,8 @@ use Tests\Fixtures\Coupon\CouponFactory; covers(PublishCampaign::class, PublishCampaignInput::class, CampaignStatusUpdated::class, CampaignNotFound::class); describe('update campaign publication use case', function (): void { - $campaignCoupon1 = new CampaignCoupon(new CouponCode('COU01'), new PrintRules(maxPerCustomer: 1)); - $campaignCoupon2 = new CampaignCoupon(new CouponCode('COU02'), new PrintRules(maxPerCustomer: 1)); + $campaignCoupon1 = new CampaignCoupon(CouponCode::generate(), new PrintRules(maxPerCustomer: 1)); + $campaignCoupon2 = new CampaignCoupon(CouponCode::generate(), new PrintRules(maxPerCustomer: 1)); $existingCampaign = static fn (CampaignCode $code, bool $basePublicationStatus): Campaign => Campaign::hydrate( CampaignId::generate(), $code, @@ -77,9 +77,9 @@ describe('update campaign publication use case', function (): void { $publication = true; $input = new PublishCampaignInput(code: $campaign->code, publication: $publication); $expectedCoupons = [ - CouponFactory::make('COU01'), - CouponFactory::make('COU02'), - CouponFactory::expired('COU03'), + CouponFactory::make(), + CouponFactory::make(), + CouponFactory::expired(), ]; $repository = \Mockery::mock(CampaignRepository::class); @@ -130,8 +130,8 @@ describe('update campaign publication use case', function (): void { $publication = true; $input = new PublishCampaignInput(code: $campaign->code, publication: $publication); $expectedCoupons = [ - CouponFactory::expired('COU01'), - CouponFactory::upcoming('COU02'), + CouponFactory::expired(), + CouponFactory::upcoming(), ]; $repository = \Mockery::mock(CampaignRepository::class); diff --git a/tests/Unit/Application/UseCases/Campaign/Update/RemoveCouponToCampaignTest.php b/tests/Unit/Application/UseCases/Campaign/Update/RemoveCouponToCampaignTest.php index f76811a..d7249f8 100644 --- a/tests/Unit/Application/UseCases/Campaign/Update/RemoveCouponToCampaignTest.php +++ b/tests/Unit/Application/UseCases/Campaign/Update/RemoveCouponToCampaignTest.php @@ -37,9 +37,9 @@ covers( ); describe('remove coupons to campaign use case', function (): void { - $coupon1 = CouponFactory::make(code: 'COU01'); - $coupon2 = CouponFactory::make(code: 'COU02'); - $coupon3 = CouponFactory::make(code: 'COU03'); + $coupon1 = CouponFactory::make(); + $coupon2 = CouponFactory::make(); + $coupon3 = CouponFactory::make(); $campaignCoupon1 = new CampaignCoupon($coupon1->code, new PrintRules(maxPerCustomer: 1)); $campaignCoupon2 = new CampaignCoupon($coupon2->code, new PrintRules(maxPerCustomer: 1)); $campaignCoupon3 = new CampaignCoupon($coupon3->code, new PrintRules(maxPerCustomer: 1)); @@ -92,7 +92,7 @@ describe('remove coupons to campaign use case', function (): void { it('returns a specific output when the coupon is not in the campaign', function () use ($existingCampaign): void { // GIVEN $campaign = $existingCampaign(CampaignCode::generate(), true); - $coupon = CouponFactory::make(code: 'COU04'); + $coupon = CouponFactory::make(); $repository = \Mockery::mock(CampaignRepository::class); $repository->shouldReceive('findByCode')->once()->andReturn($campaign); @@ -164,7 +164,7 @@ describe('remove coupons to campaign use case', function (): void { $couponRepository = \Mockery::mock(CouponRepository::class); $couponRepository->shouldReceive('findByCode')->once()->andReturn(null); - $input = new RemoveCouponToCampaignInput(code: $campaign->code, couponCode: new CouponCode('ZZZ01')); + $input = new RemoveCouponToCampaignInput(code: $campaign->code, couponCode: CouponCode::generate()); $useCase = new RemoveCouponToCampaign($repository, $couponRepository); // WHEN diff --git a/tests/Unit/Application/UseCases/CampaignRequest/Transform/TransformCampaignRequestTest.php b/tests/Unit/Application/UseCases/CampaignRequest/Transform/TransformCampaignRequestTest.php new file mode 100644 index 0000000..84b0c98 --- /dev/null +++ b/tests/Unit/Application/UseCases/CampaignRequest/Transform/TransformCampaignRequestTest.php @@ -0,0 +1,150 @@ + CampaignRequestFactory::hydrated( + status: $overrides['status'] ?? CampaignRequestStatus::CREATED, + brandCode: $brandCode, + name: $campaignName, + applicant: new Applicant('John', 'Doe', new Email('john.doe@example.com')), + campaignType: $overrides['campaignType'] ?? CampaignRequestType::CLUB, + requestedCoupon: $overrides['coupon'] ?? $requestedCoupon, + ); + + it('transforms a campaign request to a campaign and a coupon', function () use ($existingCampaignRequest, $campaignName): void { + // GIVEN + $campaignRequest = $existingCampaignRequest(); + + $campaignRequestRepository = \Mockery::mock(CampaignRequestRepository::class); + $campaignRequestRepository->shouldReceive('findById')->once()->with($campaignRequest->id)->andReturn($campaignRequest); + $campaignRequestRepository->shouldReceive('save')->once()->with(\Mockery::type(CampaignRequest::class)); + $campaignRepository = \Mockery::mock(CampaignRepository::class); + $campaignRepository->shouldReceive('save')->once()->with(\Mockery::type(Campaign::class)); + $couponRepository = \Mockery::mock(CouponRepository::class); + $couponRepository->shouldReceive('save')->once()->with(\Mockery::type(Coupon::class)); + + $input = new Input($campaignRequest->id); + $useCase = new TransformCampaignRequest($campaignRequestRepository, $campaignRepository, $couponRepository); + + // WHEN + $result = $useCase->execute($input); + + // THEN + expect($result)->toBeInstanceOf(CampaignRequestTransformed::class) + ->and($result->campaign->name->value)->toBe($campaignName) + ->and($result->status)->toBe(CampaignRequestStatus::TRANSFORMED); + }); + + it('can not tranform a campaign request which has already been transformed', function () use ($existingCampaignRequest): void { + // GIVEN + $campaignRequest = $existingCampaignRequest(['status' => CampaignRequestStatus::TRANSFORMED]); + + $campaignRequestRepository = \Mockery::mock(CampaignRequestRepository::class); + $campaignRequestRepository->shouldReceive('findById')->once()->with($campaignRequest->id)->andReturn($campaignRequest); + $campaignRequestRepository->shouldNotReceive('save'); + $campaignRepository = \Mockery::mock(CampaignRepository::class); + $campaignRepository->shouldNotReceive('save'); + $couponRepository = \Mockery::mock(CouponRepository::class); + $couponRepository->shouldNotReceive('save'); + + $input = new Input($campaignRequest->id); + $useCase = new TransformCampaignRequest($campaignRequestRepository, $campaignRepository, $couponRepository); + + // WHEN + $result = $useCase->execute($input); + + // THEN + expect($result)->toBeInstanceOf(CampaignRequestAlreadyTranformed::class); + }); + + it('returns CampaignRequestNotFound when the code is not found', function () use ($existingCampaignRequest): void { + // GIVEN + $campaignRequest = $existingCampaignRequest(['status' => CampaignRequestStatus::CREATED]); + + $campaignRequestRepository = \Mockery::mock(CampaignRequestRepository::class); + $campaignRequestRepository->shouldReceive('findById')->once()->with($campaignRequest->id)->andReturn(null); + $campaignRequestRepository->shouldNotReceive('save'); + $campaignRepository = \Mockery::mock(CampaignRepository::class); + $campaignRepository->shouldNotReceive('save'); + $couponRepository = \Mockery::mock(CouponRepository::class); + $couponRepository->shouldNotReceive('save'); + + $input = new Input($campaignRequest->id); + $useCase = new TransformCampaignRequest($campaignRequestRepository, $campaignRepository, $couponRepository); + + // WHEN + $result = $useCase->execute($input); + + // THEN + expect($result)->toBeInstanceOf(CampaignRequestNotFound::class); + }); +}); diff --git a/tests/Unit/Application/UseCases/Coupon/Create/CreateCouponTest.php b/tests/Unit/Application/UseCases/Coupon/Create/CreateCouponTest.php index 7bc475b..df25940 100644 --- a/tests/Unit/Application/UseCases/Coupon/Create/CreateCouponTest.php +++ b/tests/Unit/Application/UseCases/Coupon/Create/CreateCouponTest.php @@ -42,7 +42,6 @@ covers(CreateCoupon::class, Input::class, CouponCreated::class, CouponCodeAlread describe('create coupon use case', function (): void { $makeInput = fn (array $overrides = []): Input => new Input(...array_merge([ - 'code' => new CouponCode('COU01'), 'name' => new CouponName('Coupon name'), 'amount' => new MoneyAmount(new Money(Currency::EUR, 100)), 'picture' => new CouponPicture( @@ -79,7 +78,7 @@ describe('create coupon use case', function (): void { $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('existsByProcessingCode')->once()->with($input->processingCode->toArray()[0])->andReturn(false); $couponRepository->shouldReceive('save')->once()->with(\Mockery::type(Coupon::class)); $useCase = new CreateCoupon($couponRepository, $brandRepository); @@ -90,7 +89,7 @@ describe('create coupon use case', function (): void { // THEN expect($result)->toBeInstanceOf(CouponCreated::class) ->and($result->id)->toBeInstanceOf(CouponId::class) - ->and($result->code)->toEqual($input->code) + ->and($result->code)->toBeInstanceOf(CouponCode::class) ->and($result->name)->toEqual($input->name) ->and($result->amount)->toEqual($input->amount) ->and($result->picture)->toEqual($input->picture) @@ -113,7 +112,7 @@ describe('create coupon use case', function (): void { $brandRepository->shouldReceive('existsByCode')->once()->with($input->brandCode)->andReturn(false); $couponRepository = \Mockery::mock(CouponRepository::class); - $couponRepository->shouldNotReceive('existsByCode'); + $couponRepository->shouldNotReceive('existsByProcessingCode'); $couponRepository->shouldNotReceive('save'); $useCase = new CreateCoupon($couponRepository, $brandRepository); @@ -133,7 +132,7 @@ describe('create coupon use case', function (): void { $brandRepository->shouldReceive('existsByCode')->once()->with($input->brandCode)->andReturn(true); $couponRepository = \Mockery::mock(CouponRepository::class); - $couponRepository->shouldReceive('existsByCode')->once()->with($input->code)->andReturn(true); + $couponRepository->shouldReceive('existsByProcessingCode')->once()->with($input->processingCode->toArray()[0])->andReturn(true); $couponRepository->shouldNotReceive('save'); $useCase = new CreateCoupon($couponRepository, $brandRepository); @@ -157,7 +156,7 @@ describe('create coupon use case', function (): void { $brandRepository->shouldReceive('existsByCode')->once()->andReturn(true); $couponRepository = \Mockery::mock(CouponRepository::class); - $couponRepository->shouldReceive('existsByCode')->once()->andReturn(false); + $couponRepository->shouldReceive('existsByProcessingCode')->once()->andReturn(false); $couponRepository->shouldReceive('save')->once()->with(\Mockery::on( fn (Coupon $saved): bool => $saved->availabilityCalendar === $calendar, )); @@ -195,7 +194,7 @@ describe('create coupon use case', function (): void { $brandRepository->shouldReceive('existsByCode')->once()->andReturn(true); $couponRepository = \Mockery::mock(CouponRepository::class); - $couponRepository->shouldReceive('existsByCode')->once()->andReturn(false); + $couponRepository->shouldReceive('existsByProcessingCode')->andReturn(false); $couponRepository->shouldReceive('save')->once()->with(\Mockery::type(Coupon::class)); $useCase = new CreateCoupon($couponRepository, $brandRepository); @@ -244,7 +243,7 @@ describe('create coupon use case', function (): void { $brandRepository->shouldReceive('existsByCode')->andReturn(true); $couponRepository = \Mockery::mock(CouponRepository::class); - $couponRepository->shouldReceive('existsByCode')->andReturn(false); + $couponRepository->shouldReceive('existsByProcessingCode')->andReturn(false); $couponRepository->shouldNotReceive('save'); $useCase = new CreateCoupon($couponRepository, $brandRepository); @@ -273,7 +272,7 @@ describe('create coupon use case', function (): void { $brandRepository->shouldReceive('existsByCode')->andReturn(true); $couponRepository = \Mockery::mock(CouponRepository::class); - $couponRepository->shouldReceive('existsByCode')->andReturn(false); + $couponRepository->shouldReceive('existsByProcessingCode')->andReturn(false); $couponRepository->shouldNotReceive('save'); $useCase = new CreateCoupon($couponRepository, $brandRepository); @@ -285,6 +284,43 @@ describe('create coupon use case', function (): void { expect($action)->toThrow(\DomainException::class, 'A coupon requires validity information for BENELUX.'); }); + it('rejects a coupon with an already used processingCode (with 2 locales)', 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()->with($input->brandCode)->andReturn(true); + + $couponRepository = \Mockery::mock(CouponRepository::class); + $couponRepository->shouldReceive('existsByProcessingCode')->with($input->processingCode->toArray()[0])->andReturn(false); + $couponRepository->shouldReceive('existsByProcessingCode')->with($input->processingCode->toArray()[1])->andReturn(true); + $couponRepository->shouldNotReceive('save'); + + $useCase = new CreateCoupon($couponRepository, $brandRepository); + + // WHEN + $result = $useCase->execute($input); + + // THEN + expect($result)->toBeInstanceOf(CouponCodeAlreadyExists::class); + }); + it('rejects two processing codes for the same country', function () use ($makeInput): void { // GIVEN / WHEN $action = fn (): Input => $makeInput([ diff --git a/tests/Unit/Application/UseCases/Coupon/Find/FindCouponTest.php b/tests/Unit/Application/UseCases/Coupon/Find/FindCouponTest.php index a645884..6ad790b 100644 --- a/tests/Unit/Application/UseCases/Coupon/Find/FindCouponTest.php +++ b/tests/Unit/Application/UseCases/Coupon/Find/FindCouponTest.php @@ -56,7 +56,6 @@ describe('find coupon use case', function (): void { contact: new Email('contact@example.com'), ); $coupon = Coupon::create( - code: new CouponCode('COU01'), name: new CouponName('Coupon name'), amount: new MoneyAmount(new Money(Currency::EUR, 100)), picture: new CouponPicture( @@ -144,7 +143,7 @@ describe('find coupon use case', function (): void { it('returns CouponNotFound when the code is not found', function (): void { // GIVEN - $code = new CouponCode('COU02'); + $code = CouponCode::generate(); $couponRepository = \Mockery::mock(CouponRepository::class); $couponRepository->shouldReceive('findByCode')->once()->with($code)->andReturn(null); diff --git a/tests/Unit/Application/UseCases/Coupon/List/ListCouponTest.php b/tests/Unit/Application/UseCases/Coupon/List/ListCouponTest.php index ce75463..15b3012 100644 --- a/tests/Unit/Application/UseCases/Coupon/List/ListCouponTest.php +++ b/tests/Unit/Application/UseCases/Coupon/List/ListCouponTest.php @@ -46,7 +46,7 @@ covers(ListCoupons::class, Input::class, Output::class, CouponListItem::class); describe('list coupons use case', function (): void { $coupon1 = Coupon::hydrate( id: CouponId::generate(), - code: new CouponCode('AAA01'), + code: CouponCode::generate(), name: new CouponName('Coupon 1'), amount: new MoneyAmount(new Money(Currency::EUR, 300)), picture: new CouponPicture( @@ -77,7 +77,7 @@ describe('list coupons use case', function (): void { ); $coupon2 = Coupon::hydrate( id: CouponId::generate(), - code: new CouponCode('BBB02'), + code: CouponCode::generate(), name: new CouponName('Coupon 2'), amount: new MoneyAmount(new Money(Currency::EUR, 300)), picture: new CouponPicture( diff --git a/tests/Unit/Application/UseCases/Coupon/Update/UpdateCouponTest.php b/tests/Unit/Application/UseCases/Coupon/Update/UpdateCouponTest.php index fd45a6c..5e58be0 100644 --- a/tests/Unit/Application/UseCases/Coupon/Update/UpdateCouponTest.php +++ b/tests/Unit/Application/UseCases/Coupon/Update/UpdateCouponTest.php @@ -40,7 +40,7 @@ covers(UpdateCoupon::class, Input::class, CouponUpdated::class, CouponNotFound:: describe('update coupon use case', function (): void { $makeCoupon = static fn (): Coupon => Coupon::hydrate( id: CouponId::generate(), - code: new CouponCode('COU01'), + code: CouponCode::generate(), name: new CouponName('Coupon name'), amount: new MoneyAmount(new Money(Currency::EUR, 100)), picture: new CouponPicture( @@ -115,7 +115,7 @@ describe('update coupon use case', function (): void { it('returns CouponNotFound when the coupon does not exist', function (): void { // GIVEN - $code = new CouponCode('COU01'); + $code = CouponCode::generate(); $input = new Input( code: $code, picture: new CouponPicture( @@ -180,7 +180,7 @@ describe('update coupon use case', function (): void { it('rejects an input with nothing to update', function (): void { // GIVEN - $code = new CouponCode('COU01'); + $code = CouponCode::generate(); // WHEN $action = fn (): Input => new Input(code: $code); diff --git a/tests/Unit/Application/UseCases/PrintableCoupon/Create/CreatePrintableCouponTest.php b/tests/Unit/Application/UseCases/PrintableCoupon/Create/CreatePrintableCouponTest.php index 16e5f07..dc2caf8 100644 --- a/tests/Unit/Application/UseCases/PrintableCoupon/Create/CreatePrintableCouponTest.php +++ b/tests/Unit/Application/UseCases/PrintableCoupon/Create/CreatePrintableCouponTest.php @@ -24,37 +24,21 @@ use Domain\Models\ValueObjects\Campaign\CampaignId; use Domain\Models\ValueObjects\Campaign\CampaignName; use Domain\Models\ValueObjects\Coupon\CouponAvailabilityCalendar; use Domain\Models\ValueObjects\Coupon\CouponCode; -use Domain\Models\ValueObjects\Coupon\CouponDiffusion; -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\CrmHashcode; -use Domain\Models\ValueObjects\IncoCode; -use Domain\Models\ValueObjects\MoneyAmount; use Domain\Models\ValueObjects\Period; -use Domain\Models\ValueObjects\PicturePath; use Domain\Models\ValueObjects\PrintableCoupon\MarketingDirectCode; use Domain\Models\ValueObjects\PrintableCoupon\PrintableCouponCode; use Domain\Models\ValueObjects\PrintableCoupon\PrintedCoupon; use Domain\Models\ValueObjects\PrintableCoupon\PrintedCouponId; use Domain\Models\ValueObjects\PrintRules; -use Domain\Models\ValueObjects\ProcessingCode; -use Domain\Models\ValueObjects\ProcessingCodeArray; use Domain\Ports\Repositories\CampaignRepository; use Domain\Ports\Repositories\CouponRepository; use Domain\Ports\Repositories\PrintableCouponRepository; use Domain\Services\PrintableCoupon\PrintEligibilityChecker; use Ramsey\Uuid\Uuid; -use Shared\Enums\Country; -use Shared\Enums\Currency; use Shared\Helpers\DatesHelper; -use Shared\ValueObjects\Money; +use Tests\Fixtures\Coupon\CouponFactory; covers(CreatePrintableCoupon::class, Input::class, PrintableCouponCreated::class); @@ -62,60 +46,24 @@ describe('create coupon use case', function (): void { $crmCustomerHashcode = new CrmHashcode(Uuid::uuid4()); $brandCode = new BrandCode('AAA01'); $campaignCode = CampaignCode::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, - ); + $couponCode = CouponCode::generate(); $printRules = fn (?int $maxPerCustomer = null, ?int $cooldownDays = null): ?PrintRules => ($maxPerCustomer || $cooldownDays) ? new PrintRules( maxPerCustomer: $maxPerCustomer, cooldownDays: $cooldownDays, ) : null; - $campaignCoupon = fn (?int $maxPerCustomer = null, ?int $cooldownDays = null): CampaignCoupon => new CampaignCoupon( + + $makeCoupon = fn (array $overrides = []): Coupon => CouponFactory::make( + diffusionStart: $overrides['diffusionStart'] ?? null, + diffusionEnd: $overrides['diffusionEnd'] ?? null, + availabilityCalendar: $overrides['availabilityCalendar'] ?? null, + brandCode: $brandCode->value, + code: $couponCode, + ); + $campaignCoupon = fn (?int $maxPerCustomer = null, ?int $cooldownDays = null): CampaignCoupon => new CampaignCoupon( $couponCode, $printRules($maxPerCustomer, $cooldownDays), ); - $makeCampaign = fn (?int $maxPerCustomer = null, ?int $cooldownDays = null, $published = true): Campaign => Campaign::hydrate( + $makeCampaign = fn (?int $maxPerCustomer = null, ?int $cooldownDays = null, $published = true): Campaign => Campaign::hydrate( id: CampaignId::generate(), code: $campaignCode, name: new CampaignName('Campaign name'), @@ -127,7 +75,7 @@ describe('create coupon use case', function (): void { brandCode: $brandCode, actorId: ActorId::generate(), ); - $printedCoupon = new PrintedCoupon( + $printedCoupon = new PrintedCoupon( id: PrintedCouponId::generate(), marketingCode: new MarketingDirectCode('0000000000001'), printableCode: PrintableCouponCode::generate(), @@ -136,7 +84,7 @@ describe('create coupon use case', function (): void { customer: $crmCustomerHashcode, printedAt: DatesHelper::now(), ); - $makeInput = fn (Campaign $campaign): Input => new Input( + $makeInput = fn (Campaign $campaign): Input => new Input( campaignCode: $campaign->code, couponCode: $couponCode, crmCustomerHashcode: $crmCustomerHashcode, @@ -159,9 +107,7 @@ describe('create coupon use case', function (): void { $printableCouponRepository = \Mockery::mock(PrintableCouponRepository::class); $printableCouponRepository->shouldReceive('save')->once()->with(\Mockery::type(PrintableCoupon::class)); - $printEligibilityChecker = new PrintEligibilityChecker( - $printableCouponRepository, - ); + $printEligibilityChecker = new PrintEligibilityChecker($printableCouponRepository); $useCase = new CreatePrintableCoupon( $printableCouponRepository, @@ -193,9 +139,7 @@ describe('create coupon use case', function (): void { $printableCouponRepository = \Mockery::mock(PrintableCouponRepository::class); - $printEligibilityChecker = new PrintEligibilityChecker( - $printableCouponRepository, - ); + $printEligibilityChecker = new PrintEligibilityChecker($printableCouponRepository); $useCase = new CreatePrintableCoupon( $printableCouponRepository, @@ -223,9 +167,7 @@ describe('create coupon use case', function (): void { $printableCouponRepository = \Mockery::mock(PrintableCouponRepository::class); - $printEligibilityChecker = new PrintEligibilityChecker( - $printableCouponRepository, - ); + $printEligibilityChecker = new PrintEligibilityChecker($printableCouponRepository); $useCase = new CreatePrintableCoupon( $printableCouponRepository, @@ -256,9 +198,7 @@ describe('create coupon use case', function (): void { $printableCouponRepository = \Mockery::mock(PrintableCouponRepository::class); - $printEligibilityChecker = new PrintEligibilityChecker( - $printableCouponRepository, - ); + $printEligibilityChecker = new PrintEligibilityChecker($printableCouponRepository); $useCase = new CreatePrintableCoupon( $printableCouponRepository, @@ -291,9 +231,7 @@ describe('create coupon use case', function (): void { $printableCouponRepository = \Mockery::mock(PrintableCouponRepository::class); - $printEligibilityChecker = new PrintEligibilityChecker( - $printableCouponRepository, - ); + $printEligibilityChecker = new PrintEligibilityChecker($printableCouponRepository); $useCase = new CreatePrintableCoupon( $printableCouponRepository, @@ -326,9 +264,7 @@ describe('create coupon use case', function (): void { $printableCouponRepository = \Mockery::mock(PrintableCouponRepository::class); - $printEligibilityChecker = new PrintEligibilityChecker( - $printableCouponRepository, - ); + $printEligibilityChecker = new PrintEligibilityChecker($printableCouponRepository); $useCase = new CreatePrintableCoupon( $printableCouponRepository, @@ -360,9 +296,7 @@ describe('create coupon use case', function (): void { $printableCouponRepository = \Mockery::mock(PrintableCouponRepository::class); - $printEligibilityChecker = new PrintEligibilityChecker( - $printableCouponRepository, - ); + $printEligibilityChecker = new PrintEligibilityChecker($printableCouponRepository); $useCase = new CreatePrintableCoupon( $printableCouponRepository, @@ -410,9 +344,7 @@ describe('create coupon use case', function (): void { $printableCouponRepository = \Mockery::mock(PrintableCouponRepository::class); $printableCouponRepository->shouldReceive('save')->once()->with(\Mockery::type(PrintableCoupon::class)); - $printEligibilityChecker = new PrintEligibilityChecker( - $printableCouponRepository, - ); + $printEligibilityChecker = new PrintEligibilityChecker($printableCouponRepository); $useCase = new CreatePrintableCoupon( $printableCouponRepository, @@ -458,9 +390,7 @@ describe('create coupon use case', function (): void { $crmCustomerHashcode, )->andReturn(5); - $printEligibilityChecker = new PrintEligibilityChecker( - $printableCouponRepository, - ); + $printEligibilityChecker = new PrintEligibilityChecker($printableCouponRepository); $useCase = new CreatePrintableCoupon( $printableCouponRepository, @@ -509,9 +439,7 @@ describe('create coupon use case', function (): void { $crmCustomerHashcode, )->andReturn($printedCoupon); - $printEligibilityChecker = new PrintEligibilityChecker( - $printableCouponRepository, - ); + $printEligibilityChecker = new PrintEligibilityChecker($printableCouponRepository); $useCase = new CreatePrintableCoupon( $printableCouponRepository, diff --git a/tests/Unit/Application/UseCases/PrintableCoupon/List/ListPrintableCouponTest.php b/tests/Unit/Application/UseCases/PrintableCoupon/List/ListPrintableCouponTest.php index 8ab557f..6eea0e9 100644 --- a/tests/Unit/Application/UseCases/PrintableCoupon/List/ListPrintableCouponTest.php +++ b/tests/Unit/Application/UseCases/PrintableCoupon/List/ListPrintableCouponTest.php @@ -74,7 +74,6 @@ describe('list printable coupons use case', function (): void { ); $createCoupon = fn (CouponCode $code, CouponPicture $picture): Coupon => Coupon::create( - code: $code, name: new CouponName('Coupon name'), amount: new MoneyAmount(new Money(Currency::EUR, 100)), picture: $picture, @@ -119,7 +118,7 @@ describe('list printable coupons use case', function (): void { // GIVEN $input = $createInput(); $picture = $createPicture('picture'); - $couponCode = new CouponCode('AAA01'); + $couponCode = CouponCode::generate(); $printable = $createPrintableCoupon($couponCode); $coupon = $createCoupon($couponCode, $picture); @@ -167,8 +166,8 @@ describe('list printable coupons use case', function (): void { ); $input = $createInput($sortCriteria); - $code1 = new CouponCode('AAA01'); - $code2 = new CouponCode('BBB02'); + $code1 = CouponCode::generate(); + $code2 = CouponCode::generate(); $picture1 = $createPicture('picture-1'); $picture2 = $createPicture('picture-2'); @@ -232,7 +231,7 @@ describe('list printable coupons use case', function (): void { // GIVEN $input = $createInput(); $picture = $createPicture('picture'); - $couponCode = new CouponCode('AAA01'); + $couponCode = CouponCode::generate(); $items = [$createPrintableCoupon($couponCode), $createPrintableCoupon($couponCode)]; $repository = \Mockery::mock(PrintableCouponRepository::class); @@ -257,7 +256,7 @@ describe('list printable coupons use case', function (): void { it('throws when the coupon of a printable coupon does not exist', function () use ($createInput, $createPrintableCoupon): void { // GIVEN $input = $createInput(); - $printable = $createPrintableCoupon(new CouponCode('BBB02')); + $printable = $createPrintableCoupon(CouponCode::generate()); $repository = \Mockery::mock(PrintableCouponRepository::class); $repository->shouldReceive('findAllAvailable') diff --git a/tests/Unit/Domain/Models/Entities/CampaignTest.php b/tests/Unit/Domain/Models/Entities/CampaignTest.php index f0378aa..04e9b31 100644 --- a/tests/Unit/Domain/Models/Entities/CampaignTest.php +++ b/tests/Unit/Domain/Models/Entities/CampaignTest.php @@ -33,6 +33,7 @@ use Domain\Models\ValueObjects\PicturePath; use Domain\Models\ValueObjects\PrintRules; use Domain\Models\ValueObjects\ProcessingCode; use Domain\Models\ValueObjects\ProcessingCodeArray; +use Ramsey\Uuid\Uuid; use Shared\Enums\Country; use Shared\Enums\Currency; use Shared\ValueObjects\Money; @@ -47,19 +48,17 @@ describe('campaign', function (): void { $printAllAtOnce = \boolval(1); $coupons = new CampaignCouponArray([ new CampaignCoupon( - new CouponCode('AAA01'), + CouponCode::generate(), 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( @@ -109,7 +108,7 @@ describe('campaign', function (): void { $name = new CampaignName('Name'); $coupons = new CampaignCouponArray([ new CampaignCoupon( - new CouponCode('AAA01'), + CouponCode::generate(), new PrintRules(maxPerCustomer: 3) ) ]); @@ -173,10 +172,13 @@ describe('campaign', function (): void { $actorId, ): void { // GIVEN - $expected = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); - - $campaign = Campaign::create( + $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, @@ -371,8 +373,10 @@ describe('campaign', function (): void { it('gets the codes of the associated coupons', function (): void { // GIVEN - $expectedCoupons = ['COU01', 'COU02']; - $campaign = CampaignFactory::hydrated(couponCodes: $expectedCoupons); + $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()); @@ -429,7 +433,7 @@ describe('campaign', function (): void { // GIVEN CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC'); $id = CampaignId::generate(); - $couponCode = new CouponCode('COU09'); + $couponCode = CouponCode::generate(); $campaign = Campaign::hydrate( $id, $code, diff --git a/tests/Unit/Domain/Models/Entities/CouponTest.php b/tests/Unit/Domain/Models/Entities/CouponTest.php index 0b146c0..6885455 100644 --- a/tests/Unit/Domain/Models/Entities/CouponTest.php +++ b/tests/Unit/Domain/Models/Entities/CouponTest.php @@ -37,7 +37,7 @@ use Shared\ValueObjects\Money; covers(Coupon::class); describe('coupon', function (): void { - $code = new CouponCode('AAA01'); + $code = CouponCode::generate(); $name = new CouponName('Name'); $amount = new MoneyAmount(new Money(Currency::EUR, 1)); $picture = new CouponPicture( @@ -288,7 +288,6 @@ describe('coupon', function (): void { }); it('creates a new coupon with a freshly generated id', function () use ( - $code, $name, $amount, $picture, @@ -304,7 +303,6 @@ describe('coupon', function (): void { ): void { // GIVEN / WHEN $coupon = Coupon::create( - code: $code, name: $name, amount: $amount, picture: $picture, @@ -321,7 +319,6 @@ describe('coupon', function (): void { // THEN expect($coupon->id())->toBeInstanceOf(CouponId::class) - ->and($coupon->code)->toBe($code) ->and($coupon->name)->toBe($name) ->and($coupon->amount)->toBe($amount) ->and($coupon->picture)->toBe($picture) @@ -339,7 +336,6 @@ describe('coupon', function (): void { }); it('generates a different id for each created coupon', function () use ( - $code, $name, $amount, $picture, @@ -355,7 +351,6 @@ describe('coupon', function (): void { ): void { // GIVEN / WHEN $firstCoupon = Coupon::create( - code: $code, name: $name, amount: $amount, picture: $picture, @@ -370,7 +365,6 @@ describe('coupon', function (): void { availabilityCalendar: $availabilityCalendar, ); $secondCoupon = Coupon::create( - code: $code, name: $name, amount: $amount, picture: $picture, @@ -432,7 +426,6 @@ describe('coupon', function (): void { }); it('computes the quantity from its movements', function () use ( - $code, $name, $amount, $picture, @@ -446,7 +439,6 @@ describe('coupon', function (): void { ): void { // GIVEN $coupon = Coupon::create( - code: $code, name: $name, amount: $amount, picture: $picture, diff --git a/tests/Unit/Domain/Models/Entities/PrintableCouponTest.php b/tests/Unit/Domain/Models/Entities/PrintableCouponTest.php index 1ad7e6d..ddbdd7f 100644 --- a/tests/Unit/Domain/Models/Entities/PrintableCouponTest.php +++ b/tests/Unit/Domain/Models/Entities/PrintableCouponTest.php @@ -17,7 +17,7 @@ covers(PrintableCoupon::class); describe('printable coupon', function (): void { $campaignCode = CampaignCode::generate(); - $couponCode = new CouponCode('AAA01'); + $couponCode = CouponCode::generate(); $couponName = new CouponName('Coupon name'); $crmCustomerHashcode = new CrmHashcode(Uuid::uuid4()); $distributableUntil = new \DateTimeImmutable('2027-01-01'); diff --git a/tests/Unit/Domain/Models/Entities/StockTest.php b/tests/Unit/Domain/Models/Entities/StockTest.php index 8cdf00f..28e9031 100644 --- a/tests/Unit/Domain/Models/Entities/StockTest.php +++ b/tests/Unit/Domain/Models/Entities/StockTest.php @@ -19,7 +19,7 @@ covers(Stock::class); describe('stock', function (): void { $stockAmount = new StockAmount(10); $campaignCode = CampaignCode::generate(); - $couponCode = new CouponCode('AAA01'); + $couponCode = CouponCode::generate(); $stockAdjustment = [new StockAdjustment(5, 'Inventory correction', ActorId::generate())]; it('hydrates a stock', function () use ($stockAmount, $campaignCode, $couponCode, $stockAdjustment): void { diff --git a/tests/Unit/Domain/Models/ValueObjects/Campaign/CampaignCouponArrayTest.php b/tests/Unit/Domain/Models/ValueObjects/Campaign/CampaignCouponArrayTest.php index a8ccf46..994ac65 100644 --- a/tests/Unit/Domain/Models/ValueObjects/Campaign/CampaignCouponArrayTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/Campaign/CampaignCouponArrayTest.php @@ -13,12 +13,12 @@ covers(CampaignCouponArray::class); describe('campaign coupon array', function (): void { $coupon = new CampaignCoupon( - new CouponCode('AAA01'), + CouponCode::generate(), new PrintRules(maxPerCustomer: 3), ); $coupon2 = new CampaignCoupon( - new CouponCode('AAA02'), + CouponCode::generate(), new PrintRules(maxPerCustomer: 3), ); @@ -69,28 +69,32 @@ describe('campaign coupon array', function (): void { it('rejects the same coupon twice at initialisation', function (): void { // GIVEN - $coupons = [ - new CampaignCoupon(new CouponCode('AAA01')), - new CampaignCoupon(new CouponCode('AAA01')), + $couponCode = CouponCode::generate(); + $coupons = [ + new CampaignCoupon($couponCode), + new CampaignCoupon($couponCode), ]; // WHEN $action = fn (): CampaignCouponArray => new CampaignCouponArray($coupons); //WHEN / THEN - expect($action)->toThrow(\DomainException::class, '"AAA01" is present more than once.'); + expect($action)->toThrow(\DomainException::class, sprintf( + '"%s" is present more than once.', + $couponCode + )); }); it('adds a coupon to a campaign coupon array', function (): void { // GIVEN $coupons = [ - new CampaignCoupon(new CouponCode('AAA01')), - new CampaignCoupon(new CouponCode('AAA02')), + new CampaignCoupon(CouponCode::generate()), + new CampaignCoupon(CouponCode::generate()), ]; $array = new CampaignCouponArray($coupons); // WHEN - $array = $array->addCoupon(new CampaignCoupon(new CouponCode('AAA03'))); + $array = $array->addCoupon(new CampaignCoupon(CouponCode::generate())); // THEN expect($array->toArray())->toHaveCount(3); @@ -110,17 +114,21 @@ describe('campaign coupon array', function (): void { it('cant remove a coupon which is not on the campaign', function (): void { // GIVEN - $coupons = [ - new CampaignCoupon(new CouponCode('AAA01')), - new CampaignCoupon(new CouponCode('AAA02')), + $coupons = [ + new CampaignCoupon(CouponCode::generate()), + new CampaignCoupon(CouponCode::generate()), ]; - $array = new CampaignCouponArray($coupons); + $array = new CampaignCouponArray($coupons); + $couponCodeToRemove = CouponCode::generate(); // WHEN - $action = fn () => $array->removeCoupon(new CouponCode('AAA03')); + $action = fn () => $array->removeCoupon($couponCodeToRemove); // THEN - expect($action)->toThrow(\DomainException::class, '"AAA03" is not in the set.'); + expect($action)->toThrow(\DomainException::class, sprintf( + '"%s" is not in the set.', + $couponCodeToRemove + )); }); it('returns all coupon codes', function () use ($coupon, $coupon2): void { diff --git a/tests/Unit/Domain/Models/ValueObjects/Campaign/CampaignCouponTest.php b/tests/Unit/Domain/Models/ValueObjects/Campaign/CampaignCouponTest.php index b8151d6..2766947 100644 --- a/tests/Unit/Domain/Models/ValueObjects/Campaign/CampaignCouponTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/Campaign/CampaignCouponTest.php @@ -11,7 +11,7 @@ use Domain\Models\ValueObjects\PrintRules; covers(CampaignCoupon::class); describe('campaign coupon', function (): void { - $couponCode = new CouponCode('AAA01'); + $couponCode = CouponCode::generate(); $printRules = new PrintRules(maxPerCustomer: 3); it('creates a valid campaign coupon', function () use ($couponCode, $printRules): void { @@ -45,7 +45,7 @@ describe('campaign coupon', function (): void { // WHEN $result = $campaignCoupon->hasCouponCode( - new CouponCode('AAA01'), + $couponCode, ); // THEN @@ -61,7 +61,7 @@ describe('campaign coupon', function (): void { // WHEN $result = $campaignCoupon->hasCouponCode( - new CouponCode('BBB02'), + CouponCode::generate(), ); // THEN diff --git a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCodeTest.php b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCodeTest.php index a139b96..475404d 100644 --- a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCodeTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCodeTest.php @@ -5,71 +5,43 @@ declare(strict_types=1); namespace Tests\Unit\Domain\Models\ValueObjects\Coupon; use Domain\Models\ValueObjects\Coupon\CouponCode; +use Ramsey\Uuid\Uuid; covers(CouponCode::class); describe('coupon code', function (): void { - it('creates a valid coupon code', function (): void { - // GIVEN - $value = 'AAA01'; - - // WHEN - $code = new CouponCode($value); + it('generates a valid uuid v7 code', function (): void { + // GIVEN / WHEN + $code = CouponCode::generate(); // THEN - expect($code)->toBeInstanceOf(CouponCode::class) - ->and($code->value)->toBe($value); + expect(Uuid::isValid($code->value))->toBeTrue() + ->and(Uuid::fromString($code->value)->getVersion())->toBe(7); + }); + + it('generates a different code each time', function (): void { + // GIVEN / WHEN + $first = CouponCode::generate(); + $second = CouponCode::generate(); + + // THEN + expect($first->value)->not->toBe($second->value); + }); + + it('wraps a given uuid v7', function (): void { + // GIVEN + $uuid = Uuid::uuid7(); + + // WHEN + $code = new CouponCode($uuid); + + // THEN + expect($code->value)->toBe($uuid->toString()); + }); + + it('rejects a uuid that is not v7', function (): void { + // GIVEN / WHEN / THEN + expect(fn (): CouponCode => new CouponCode(Uuid::uuid4())) + ->toThrow(\DomainException::class); }); }); - -describe('coupon code validation rules', function (): void { - it('rejects invalid coupon codes', function (string $invalid): void { - // WHEN / THEN - expect(fn (): CouponCode => new CouponCode($invalid))->toThrow(\DomainException::class); - - // GIVEN - })->with([ - // rejects empty strings - '', - ' ', - "\t", - "\n", - // rejects numbers only - '12345', - '67890', - // rejects letters only - 'ABCDE', - 'FGHIJ', - // rejects codes with special characters - 'AB@12', - 'CD#34', - 'EF$56', - // rejects codes that have the same digit repeated - 'ABC11', - 'DEF22', - 'GHI33', - // rejects codes with lowercase letters - 'AbC12', - 'dEf34', - 'GhI56', - ]); - - it('rejects codes with more/less than 3 letters or more/less than 2 digits', function (string $invalid): void { - // WHEN / THEN - expect(fn (): CouponCode => new CouponCode($invalid))->toThrow(\DomainException::class); - - // GIVEN - })->with([ - 'ABCD12', - 'ABC123', - 'ABCDE123', - 'AB12', - 'A1', - 'AB1', - 'A12', - 'AB123', - 'A123', - 'ABC1', - 'AB12C', - ]); -}); diff --git a/tests/Unit/Domain/Models/ValueObjects/PrintableCoupon/PrintedCouponTest.php b/tests/Unit/Domain/Models/ValueObjects/PrintableCoupon/PrintedCouponTest.php index 2d36a46..468693c 100644 --- a/tests/Unit/Domain/Models/ValueObjects/PrintableCoupon/PrintedCouponTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/PrintableCoupon/PrintedCouponTest.php @@ -18,7 +18,7 @@ covers(PrintedCoupon::class); describe('printed coupon', function (): void { $printableCode = PrintableCouponCode::generate(); $marketingCode = new MarketingDirectCode('0000000000001'); - $couponCode = new CouponCode('AAA01'); + $couponCode = CouponCode::generate(); $campaignCode = CampaignCode::generate(); $customer = new CrmHashcode(Uuid::uuid4()); diff --git a/tests/Unit/Domain/Models/ValueObjects/PrintableCoupon/UnprintedCouponTest.php b/tests/Unit/Domain/Models/ValueObjects/PrintableCoupon/UnprintedCouponTest.php index b94e994..84f229c 100644 --- a/tests/Unit/Domain/Models/ValueObjects/PrintableCoupon/UnprintedCouponTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/PrintableCoupon/UnprintedCouponTest.php @@ -16,7 +16,7 @@ covers(UnprintedCoupon::class); describe('unprinted coupon', function (): void { $printableCode = PrintableCouponCode::generate(); - $couponCode = new CouponCode('AAA01'); + $couponCode = CouponCode::generate(); $campaignCode = CampaignCode::generate(); $customer = new CrmHashcode(Uuid::uuid4()); diff --git a/tests/Unit/Domain/Services/CampaignAvailabilityCheckerTest.php b/tests/Unit/Domain/Services/CampaignAvailabilityCheckerTest.php index 7ed1db4..a2cc8ac 100644 --- a/tests/Unit/Domain/Services/CampaignAvailabilityCheckerTest.php +++ b/tests/Unit/Domain/Services/CampaignAvailabilityCheckerTest.php @@ -12,25 +12,16 @@ use Tests\Fixtures\Coupon\CouponFactory; covers(CampaignAvailabilityChecker::class); describe('campaign availability checker', function (): void { - $makeRepository = static function (array $campaignCodes, array $coupons): CouponRepository { + it('is available with an active coupon', function (): void { + // GIVEN + $coupon = CouponFactory::make(); + $campaign = CampaignFactory::hydrated(couponCodes: [$coupon->code]); + $repository = \Mockery::mock(CouponRepository::class); - $repository->shouldReceive('findByCodes') - ->once() - ->with(\Mockery::on( - static fn (array $codes): bool => array_map( - static fn ($code): string => $code->value, - $codes, - ) === $campaignCodes, - )) - ->andReturn($coupons); + $repository->shouldReceive('findByCodes')->once()->with($campaign->couponCodes())->andReturn([ + $coupon + ]); - return $repository; - }; - - it('is available with an active coupon', function () use ($makeRepository): void { - // GIVEN - $campaign = CampaignFactory::hydrated(couponCodes: ['COU01']); - $repository = $makeRepository(['COU01'], [CouponFactory::make('COU01')]); $service = new CampaignAvailabilityChecker($repository); // WHEN @@ -40,14 +31,19 @@ describe('campaign availability checker', function (): void { expect($result)->toBeTrue(); }); - it('is available with one active and one expired coupon', function () use ($makeRepository): void { + it('is available with one active and one expired coupon', function (): void { // GIVEN - $campaign = CampaignFactory::hydrated(couponCodes: ['COU01', 'COU02']); - $repository = $makeRepository(['COU01', 'COU02'], [ - CouponFactory::expired('COU01'), - CouponFactory::make('COU02'), + $expiredCoupon = CouponFactory::expired(); + $validCoupon = CouponFactory::make(); + $campaign = CampaignFactory::hydrated(couponCodes: [$expiredCoupon->code, $validCoupon->code]); + + $repository = \Mockery::mock(CouponRepository::class); + $repository->shouldReceive('findByCodes')->once()->with($campaign->couponCodes())->andReturn([ + $expiredCoupon, + $validCoupon ]); - $service = new CampaignAvailabilityChecker($repository); + + $service = new CampaignAvailabilityChecker($repository); // WHEN $result = $service->hasActiveCoupon($campaign); @@ -56,14 +52,19 @@ describe('campaign availability checker', function (): void { expect($result)->toBeTrue(); }); - it('is not available when every coupon is expired or upcoming', function () use ($makeRepository): void { + it('is not available when every coupon is expired or upcoming', function (): void { // GIVEN - $campaign = CampaignFactory::hydrated(couponCodes: ['COU01', 'COU02']); - $repository = $makeRepository(['COU01', 'COU02'], [ - CouponFactory::expired('COU01'), - CouponFactory::upcoming('COU02'), + $expiredCoupon = CouponFactory::expired(); + $upcomingCoupon = CouponFactory::upcoming(); + $campaign = CampaignFactory::hydrated(couponCodes: [$expiredCoupon->code, $upcomingCoupon->code]); + + $repository = \Mockery::mock(CouponRepository::class); + $repository->shouldReceive('findByCodes')->once()->with($campaign->couponCodes())->andReturn([ + $expiredCoupon, + $upcomingCoupon ]); - $service = new CampaignAvailabilityChecker($repository); + + $service = new CampaignAvailabilityChecker($repository); // WHEN $result = $service->hasActiveCoupon($campaign); @@ -72,11 +73,14 @@ describe('campaign availability checker', function (): void { expect($result)->toBeFalse(); }); - it('is not available when no coupon is found', function () use ($makeRepository): void { + it('is not available when no coupon is found', function (): void { // GIVEN - $campaign = CampaignFactory::hydrated(couponCodes: ['COU01']); - $repository = $makeRepository(['COU01'], []); - $service = new CampaignAvailabilityChecker($repository); + $campaign = CampaignFactory::hydrated(); + + $repository = \Mockery::mock(CouponRepository::class); + $repository->shouldReceive('findByCodes')->once()->with($campaign->couponCodes())->andReturn([]); + + $service = new CampaignAvailabilityChecker($repository); // WHEN $result = $service->hasActiveCoupon($campaign);