From 269ff25f0ffaf685478e1a50cb8b8432fd0926c8 Mon Sep 17 00:00:00 2001 From: K Muller Date: Tue, 22 Sep 2026 16:29:34 +0200 Subject: [PATCH] fix: :bug: add missing rejection field in campaignRequest --- .../Models/Entities/CampaignRequest.php | 14 +++- .../CampaignRequest/RejectionReason.php | 18 +++++ .../Models/Entities/CampaignRequestTest.php | 70 +++++++++++++++++-- .../CampaignRequest/RejectionReasonTest.php | 34 +++++++++ 4 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 src/Domain/Models/ValueObjects/CampaignRequest/RejectionReason.php create mode 100644 tests/Unit/Domain/Models/ValueObjects/CampaignRequest/RejectionReasonTest.php diff --git a/src/Domain/Models/Entities/CampaignRequest.php b/src/Domain/Models/Entities/CampaignRequest.php index 6512dff..127885b 100644 --- a/src/Domain/Models/Entities/CampaignRequest.php +++ b/src/Domain/Models/Entities/CampaignRequest.php @@ -10,6 +10,7 @@ use Domain\Enums\Country; use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestId; use Domain\Models\ValueObjects\CampaignRequest\Applicant; +use Domain\Models\ValueObjects\CampaignRequest\RejectionReason; use Domain\Models\ValueObjects\PrintRules; use Shared\DDD\Models\AggregateRoot; use Shared\DDD\Models\HasTimestampIdentifier; @@ -28,8 +29,17 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent public ?PrintRules $printRules, public RequestedCoupon $requestedCoupon, public CampaignStatus $status, + public ?RejectionReason $rejectionReason = null, public ?\DateTimeInterface $updatedAt = null, - ) {} + ) { + if ($this->status === CampaignStatus::REJECTED && ! $this->rejectionReason instanceof RejectionReason) { + throw new \DomainException('A rejection reason is required when the campaign request is rejected.'); + } + + if ($this->status !== CampaignStatus::REJECTED && $this->rejectionReason instanceof RejectionReason) { + throw new \DomainException('A rejection reason can only be set when the campaign request is rejected.'); + } + } public function id(): CampaignRequestId { @@ -56,6 +66,7 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent ?PrintRules $printRules, RequestedCoupon $requestedCoupon, CampaignStatus $status, + ?RejectionReason $rejectionReason = null, ?\DateTimeInterface $updatedAt = null, ): self { return new self( @@ -67,6 +78,7 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent printRules: $printRules, requestedCoupon: $requestedCoupon, status: $status, + rejectionReason: $rejectionReason, updatedAt: $updatedAt, ); } diff --git a/src/Domain/Models/ValueObjects/CampaignRequest/RejectionReason.php b/src/Domain/Models/ValueObjects/CampaignRequest/RejectionReason.php new file mode 100644 index 0000000..73e4658 --- /dev/null +++ b/src/Domain/Models/ValueObjects/CampaignRequest/RejectionReason.php @@ -0,0 +1,18 @@ +and($campaignRequest->updatedAt())->toBe($updatedAt); }); + it('hydrates a rejected campaign request with a rejection reason', function (): void { + // GIVEN + $rejectionReason = new RejectionReason('Missing legal documents.'); + + // WHEN + $campaignRequest = CampaignRequest::hydrate( + CampaignRequestId::generate(), + new BrandCode('AAA01'), + new Applicant('John', 'Doe', new Email('john.doe@example.com')), + Country::FR, + CampaignType::CLUB, + new PrintRules(maxPerCustomer: 3), + aRequestedCoupon(), + CampaignStatus::REJECTED, + $rejectionReason, + ); + + // THEN + expect($campaignRequest->status)->toBe(CampaignStatus::REJECTED) + ->and($campaignRequest->rejectionReason)->toBe($rejectionReason); + }); + + it('rejects a rejected campaign request without a rejection reason', function (): void { + // WHEN + $action = fn (): CampaignRequest => CampaignRequest::hydrate( + CampaignRequestId::generate(), + new BrandCode('AAA01'), + new Applicant('John', 'Doe', new Email('john.doe@example.com')), + Country::FR, + CampaignType::CLUB, + new PrintRules(maxPerCustomer: 3), + aRequestedCoupon(), + CampaignStatus::REJECTED, + ); + + // THEN + expect($action)->toThrow( + \DomainException::class, + 'A rejection reason is required when the campaign request is rejected.' + ); + }); + + it('rejects a non-rejected campaign request with a rejection reason', function (): void { + // WHEN + $action = fn (): CampaignRequest => CampaignRequest::hydrate( + CampaignRequestId::generate(), + new BrandCode('AAA01'), + new Applicant('John', 'Doe', new Email('john.doe@example.com')), + Country::FR, + CampaignType::CLUB, + new PrintRules(maxPerCustomer: 3), + aRequestedCoupon(), + CampaignStatus::CREATED, + new RejectionReason('Missing legal documents.'), + ); + + // THEN + expect($action)->toThrow( + \DomainException::class, + 'A rejection reason can only be set when the campaign request is rejected.' + ); + }); + it('returns the creation date extracted from the id', function (): void { // GIVEN $expected = new \DateTimeImmutable('2024-06-15 12:34:56.789000', new \DateTimeZone('UTC')); diff --git a/tests/Unit/Domain/Models/ValueObjects/CampaignRequest/RejectionReasonTest.php b/tests/Unit/Domain/Models/ValueObjects/CampaignRequest/RejectionReasonTest.php new file mode 100644 index 0000000..817d894 --- /dev/null +++ b/tests/Unit/Domain/Models/ValueObjects/CampaignRequest/RejectionReasonTest.php @@ -0,0 +1,34 @@ +toBeInstanceOf(RejectionReason::class) + ->and($reason->value)->toBe($value); + }); + + it('rejects an empty or whitespace-only reason', function (string $value): void { + // WHEN + $action = fn (): RejectionReason => new RejectionReason($value); + + // THEN + expect($action)->toThrow(\DomainException::class, 'Rejection reason cannot be empty.'); + })->with([ + 'empty' => '', + 'spaces' => ' ', + ]); +});