requestedCoupon = new RequestedCoupon( new CouponName('Coupon A'), new RequestedCouponQuantity(100), new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01')), new MoneyAmount(new Money(Currency::EUR, 300)), new ProcessingCode('PC1'), new CouponValidity(30, new DateTime('2026-02-01')), [ new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE), new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::BENELUX) ], [new IncoCode(35784), new IncoCode(15522)], new PrintRules(maxPerCustomer: 3), new GS1('01234567890128', BarcodeFormat::EAN13) ); }); afterEach(function (): void { CarbonImmutable::setTestNow(); }); it('rejects a campaign request and persists it', function (): void { // GIVEN CarbonImmutable::setTestNow('2026-06-15 12:00:00 UTC'); $id = CampaignRequestId::generate(); $campaignRequest = CampaignRequest::hydrate( $id, new BrandCode('AAA01'), new CampaignName('Campaign A'), new Applicant('John', 'Doe', new Email('john.doe@example.com')), CampaignRequestType::CLUB, $this->requestedCoupon, CampaignRequestStatus::CREATED, ); $reason = new RejectionReason('Missing legal documents.'); $repository = \Mockery::mock(CampaignRequestRepository::class); $repository->shouldReceive('findById')->once()->with($id)->andReturn($campaignRequest); $repository->shouldReceive('save')->once()->with(\Mockery::on( fn (CampaignRequest $saved): bool => $saved->status === CampaignRequestStatus::REJECTED && $saved->rejectionReason === $reason && $saved->updatedAt !== null, )); $useCase = new RejectCampaignRequest($repository); // WHEN $result = $useCase->execute(new Input(id: $id, reason: $reason)); // THEN expect($result)->toBeInstanceOf(CampaignRequestRejected::class) ->and($result->id)->toBe($id) ->and($result->status)->toBe(CampaignRequestStatus::REJECTED) ->and($result->rejectionReason)->toBe($reason) ->and($result->updatedAt->format('Y-m-d H:i:s'))->toBe('2026-06-15 12:00:00'); }); it('returns CampaignRequestNotFound when the id is not found', function (): void { // GIVEN $id = CampaignRequestId::generate(); $repository = \Mockery::mock(CampaignRequestRepository::class); $repository->shouldReceive('findById')->once()->with($id)->andReturn(null); $repository->shouldNotReceive('save'); $useCase = new RejectCampaignRequest($repository); // WHEN $result = $useCase->execute(new Input(id: $id, reason: new RejectionReason('Missing legal documents.'))); // THEN expect($result)->toBeInstanceOf(CampaignRequestNotFound::class); }); it('propagates the domain exception when the campaign request cannot be rejected', function (): void { // GIVEN $id = CampaignRequestId::generate(); $campaignRequest = CampaignRequest::hydrate( $id, new BrandCode('AAA01'), new CampaignName('Campaign A'), new Applicant('John', 'Doe', new Email('john.doe@example.com')), CampaignRequestType::CLUB, $this->requestedCoupon, CampaignRequestStatus::TRANSFORMED, ); $repository = \Mockery::mock(CampaignRequestRepository::class); $repository->shouldReceive('findById')->once()->with($id)->andReturn($campaignRequest); $repository->shouldNotReceive('save'); $useCase = new RejectCampaignRequest($repository); // WHEN $action = fn (): Output => $useCase->execute( new Input(id: $id, reason: new RejectionReason('Missing legal documents.')) ); // THEN expect($action)->toThrow(\DomainException::class, 'Only a created campaign request can be rejected.'); }); it('never touches the repository when a rejection reason is missing', function (): void { // GIVEN $repository = \Mockery::mock(CampaignRequestRepository::class); $repository->shouldNotReceive('findById'); $repository->shouldNotReceive('save'); $useCase = new RejectCampaignRequest($repository); // WHEN $action = fn (): Output => $useCase->execute(new Input(id: CampaignRequestId::generate())); // THEN expect($action)->toThrow(\ArgumentCountError::class); }); });