requestedCoupon = new RequestedCoupon( new CouponName('Coupon A'), new CouponProductName('Product A'), new RequestedCouponQuantity(100), new WarningThresholdQuantity(10), new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01')), new MoneyAmount(new Money(Currency::EUR, 300)), new ProcessingCode('PC1'), new CouponPicture(new PicturePath('coupons/product.jpg'), new PicturePath('coupons/thumbnail.jpg')), new GS1('01234567890128', BarcodeFormat::EAN13), new CouponValidity(30, new DateTime()), ); }); it('rejects a campaign request and persists it', function (): void { // GIVEN CarbonImmutable::setTestNow('2026-06-15 12:00:00'); $id = CampaignRequestId::generate(); $campaignRequest = CampaignRequest::hydrate( $id, new BrandCode('AAA01'), new Applicant('John', 'Doe', new Email('john.doe@example.com')), new CountryAuthorized(Country::FRANCE), CampaignRequestType::CLUB, new PrintRules(maxPerCustomer: 3), $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 Applicant('John', 'Doe', new Email('john.doe@example.com')), new CountryAuthorized(Country::FRANCE), CampaignRequestType::CLUB, new PrintRules(maxPerCustomer: 3), $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); }); });