voucherValidator = $this->createStub(VoucherValidator::class); $travel = new Travel(); $travel->id = 12500; $this->participant = new ParticipantDto(); $this->participant->index = 0; $this->bookingDto = new BookingDto($travel, 1); $this->bookingDto->participants = [$this->participant]; } private function handler(?VoucherValidator $voucherValidator = null): ParticipantVoucherFieldHandler { $priceCalculator = $this->createStub(BookingPriceCalculator::class); $priceCalculator->method('calculateIndividualParticipantPrice')->willReturn(500.0); return new ParticipantVoucherFieldHandler( $voucherValidator ?? $this->voucherValidator, $priceCalculator, new VoucherCodeClassifier(['LW0705' => '2099-12-31']), ); } private static function promoVoucher(string $code): PromoVoucher { return new PromoVoucher($code, 'Aktion', PromoVoucher::APPLICABILITY_PER_PERSON, 50.0, 0.0, 0.0); } private static function purchaseVoucher(string $code, string $type = PurchaseVoucher::TYPE_PURCHASE): PurchaseVoucher { return new PurchaseVoucher('80190', $code, 1, 100.0, $type); } public function testRunsOnlyInCreateMode(): void { self::assertTrue($this->handler()->shouldProcess([], BookingDto::MODE_CREATE, 0)); self::assertFalse($this->handler()->shouldProcess([], BookingDto::MODE_EDIT, 0)); } public function testAppliesPrefixedPromoCodeAndClearsTheField(): void { $voucher = self::promoVoucher('R-SOMMER26'); $this->voucherValidator->method('validatePromoVoucher') ->willReturn(new PromoVoucherValidationResult(isValid: true, voucher: $voucher)); $this->handler()->processField(['voucherCode' => ' r-sommer26 '], $this->bookingDto, 0); self::assertSame('R-SOMMER26', $this->participant->promoVoucherCode); self::assertSame($voucher, $this->participant->validatedPromoVoucher); self::assertNull($this->participant->voucherCode); self::assertNull($this->participant->voucherCodeError); self::assertNull($this->participant->purchaseVoucherCode); } public function testAppliesLegacyPromoCodeAsPromo(): void { $voucherValidator = $this->createMock(VoucherValidator::class); $voucherValidator->expects(self::once())->method('validatePromoVoucher') ->with('LW0705', 12500, 500.0) ->willReturn(new PromoVoucherValidationResult(isValid: true, voucher: self::promoVoucher('LW0705'))); $voucherValidator->expects(self::never())->method('validatePurchaseVoucher'); $this->handler($voucherValidator)->processField(['voucherCode' => 'lw0705'], $this->bookingDto, 0); self::assertSame('LW0705', $this->participant->promoVoucherCode); } public function testAppliesPurchaseCode(): void { $voucher = self::purchaseVoucher('1F7V6PADRZ'); $this->voucherValidator->method('validatePurchaseVoucher') ->willReturn(new PurchaseVoucherValidationResult(isValid: true, voucher: $voucher)); $this->handler()->processField(['voucherCode' => '1f7v6padrz'], $this->bookingDto, 0); self::assertSame('1F7V6PADRZ', $this->participant->purchaseVoucherCode); self::assertSame($voucher, $this->participant->validatedPurchaseVoucher); self::assertNull($this->participant->voucherCode); self::assertFalse($this->participant->hasGoodwillVoucher()); } public function testAppliesGoodwillCodeAsPurchaseCode(): void { $this->voucherValidator->method('validatePurchaseVoucher') ->willReturn(new PurchaseVoucherValidationResult(isValid: true, voucher: self::purchaseVoucher('KULANZ', PurchaseVoucher::TYPE_GOODWILL))); $this->handler()->processField(['voucherCode' => 'KULANZ'], $this->bookingDto, 0); self::assertSame('KULANZ', $this->participant->purchaseVoucherCode); self::assertTrue($this->participant->hasGoodwillVoucher()); } public function testRejectedCodeStaysInTheFieldWithBusProsReason(): void { $this->voucherValidator->method('validatePurchaseVoucher') ->willReturn(new PurchaseVoucherValidationResult(isValid: false, errorMessage: 'Gutschein nicht gefunden')); $this->handler()->processField(['voucherCode' => 'abcdef'], $this->bookingDto, 0); self::assertSame('abcdef', $this->participant->voucherCode); self::assertSame('Gutschein ungültig: Gutschein nicht gefunden', $this->participant->voucherCodeError); self::assertNull($this->participant->purchaseVoucherCode); } public function testMalformedCodeIsRejectedWithoutAskingBusPro(): void { $voucherValidator = $this->createMock(VoucherValidator::class); $voucherValidator->expects(self::never())->method('validatePurchaseVoucher'); $voucherValidator->expects(self::never())->method('validatePromoVoucher'); $this->handler($voucherValidator)->processField(['voucherCode' => 'X-123'], $this->bookingDto, 0); self::assertSame('X-123', $this->participant->voucherCode); self::assertNotNull($this->participant->voucherCodeError); } public function testCodeAlreadyAppliedIsRejected(): void { $this->participant->purchaseVoucherCode = '1F7V6PADRZ'; $voucherValidator = $this->createMock(VoucherValidator::class); $voucherValidator->expects(self::never())->method('validatePurchaseVoucher'); $this->handler($voucherValidator)->processField(['voucherCode' => '1f7v6padrz'], $this->bookingDto, 0); self::assertSame('Dieser Code ist bereits eingelöst.', $this->participant->voucherCodeError); } public function testSecondPurchaseCodeIsRejectedAndTheFirstKept(): void { $this->participant->purchaseVoucherCode = '1F7V6PADRZ'; $voucherValidator = $this->createMock(VoucherValidator::class); $voucherValidator->expects(self::never())->method('validatePurchaseVoucher'); $this->handler($voucherValidator)->processField(['voucherCode' => '1VDHQWPZHK'], $this->bookingDto, 0); self::assertSame('1F7V6PADRZ', $this->participant->purchaseVoucherCode); self::assertSame('1VDHQWPZHK', $this->participant->voucherCode); self::assertStringContainsString('1F7V6PADRZ', (string) $this->participant->voucherCodeError); } public function testSecondPromoCodeIsRejectedAndTheFirstKept(): void { $applied = self::promoVoucher('R-SOMMER26'); $this->participant->promoVoucherCode = 'R-SOMMER26'; $this->participant->validatedPromoVoucher = $applied; $this->voucherValidator->method('validatePromoVoucher') ->willReturn(new PromoVoucherValidationResult(isValid: true, voucher: $applied)); $this->handler()->processField(['voucherCode' => 'R-WINTER26'], $this->bookingDto, 0); self::assertSame('R-SOMMER26', $this->participant->promoVoucherCode); self::assertSame($applied, $this->participant->validatedPromoVoucher); self::assertStringContainsString('R-SOMMER26', (string) $this->participant->voucherCodeError); } public function testPromoAndPurchaseCodeCanBeCombined(): void { $this->participant->purchaseVoucherCode = '1F7V6PADRZ'; $this->voucherValidator->method('validatePromoVoucher') ->willReturn(new PromoVoucherValidationResult(isValid: true, voucher: self::promoVoucher('R-SOMMER26'))); $this->handler()->processField(['voucherCode' => 'R-SOMMER26'], $this->bookingDto, 0); self::assertSame('1F7V6PADRZ', $this->participant->purchaseVoucherCode); self::assertSame('R-SOMMER26', $this->participant->promoVoucherCode); self::assertNull($this->participant->voucherCodeError); } public function testRemovesAnAppliedCode(): void { $this->participant->purchaseVoucherCode = '1F7V6PADRZ'; $this->participant->validatedPurchaseVoucher = self::purchaseVoucher('1F7V6PADRZ'); $this->participant->removeVoucherCode = '1F7V6PADRZ'; $this->handler()->processField(['voucherCode' => '', 'removeVoucherCode' => '1f7v6padrz'], $this->bookingDto, 0); self::assertNull($this->participant->purchaseVoucherCode); self::assertNull($this->participant->validatedPurchaseVoucher); self::assertNull($this->participant->removeVoucherCode); } public function testAppliedPromoCodeRejectedOnRecheckKeepsTheCodeWithItsReason(): void { $this->participant->promoVoucherCode = 'R-SOMMER26'; $this->participant->validatedPromoVoucher = self::promoVoucher('R-SOMMER26'); $this->voucherValidator->method('validatePromoVoucher') ->willReturn(new PromoVoucherValidationResult(isValid: false, errorMessage: 'Mindestpreis nicht erreicht')); $this->handler()->processField([], $this->bookingDto, 0); self::assertSame('R-SOMMER26', $this->participant->promoVoucherCode); self::assertNull($this->participant->validatedPromoVoucher); self::assertSame('Aktionscode ungültig: Mindestpreis nicht erreicht', $this->participant->promoVoucherError); } public function testCodeLeftFromAnEarlierRequestIsDroppedWhenTheFieldIsNotSubmitted(): void { $this->participant->voucherCode = 'ABCDEF'; $this->participant->voucherCodeError = 'Gutschein ungültig: Gutschein nicht gefunden'; $this->handler()->processField([], $this->bookingDto, 0); self::assertNull($this->participant->voucherCode); self::assertNull($this->participant->voucherCodeError); } }