feat: voucher purchase flow
This commit is contained in:
1 parent
a628e96d06
commit
f3dd122678
70 files changed
+3672
-122
No files matched your search
@@ -6,6 +6,7 @@ namespace App\Tests\BusProNet;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\DataProcessor\BookingDataProcessor;
|
||||
use App\BusProNet\DataProcessor\VoucherSalePayloadBuilder;
|
||||
use App\BusProNet\Exception\ApiClientException;
|
||||
use App\BusProNet\Exception\ImmediateConnectionCloseException;
|
||||
use App\BusProNet\XmlParser\ApiResponseParser;
|
||||
@@ -34,6 +35,7 @@ class ApiClientReceiveTest extends TestCase
|
||||
$this->createStub(FilesystemOperator::class),
|
||||
$this->logger,
|
||||
$this->createStub(BookingDataProcessor::class),
|
||||
new VoucherSalePayloadBuilder(),
|
||||
new RequestStack(),
|
||||
new RequestIdGenerator(),
|
||||
[
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\BusProNet\DataProcessor;
|
||||
|
||||
use App\BusProNet\Constants;
|
||||
use App\BusProNet\DataProcessor\VoucherSalePayloadBuilder;
|
||||
use App\BusProNet\Model\VoucherPaymentMethod;
|
||||
use App\BusProNet\Model\VoucherTemplate;
|
||||
use App\Form\Model\VoucherPurchaseDto;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||
|
||||
class VoucherSalePayloadBuilderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* The shape the test system accepted with <buchung>möglich</buchung> on 2026-10-01.
|
||||
*/
|
||||
public function testTheSalePayloadMatchesTheProbedRequest(): void
|
||||
{
|
||||
$payload = (new VoucherSalePayloadBuilder())->buildSalePayload($this->createDto(), Constants::BOOKING_TYPE_INQUIRY);
|
||||
|
||||
$xml = (new XmlEncoder())->encode(
|
||||
['satz' => ['@typ' => 'GUTSCHEINBUCHUNG']] + $payload,
|
||||
'xml',
|
||||
[XmlEncoder::ROOT_NODE_NAME => 'anfrage', XmlEncoder::ENCODING => 'UTF-8'],
|
||||
);
|
||||
|
||||
self::assertStringContainsString(
|
||||
'<satz typ="GUTSCHEINBUCHUNG"/><buchungsart>Anfrage</buchungsart><gutscheinart>K</gutscheinart>'
|
||||
.'<idgutscheinvorlage>3</idgutscheinvorlage><kaeufer>Probe Kaeufer</kaeufer><empfaenger>Probe Empfaenger</empfaenger>'
|
||||
.'<kaufpreis>25</kaufpreis><text></text>'
|
||||
.'<rechnungsempfänger><name>Probe</name><vorname>Myep</vorname><anrede>Herr</anrede><titel/><geschlecht></geschlecht><geburtsdatum></geburtsdatum>'
|
||||
.'<anschrift><strasse>Teststr. 1</strasse><plz>12345</plz><ort>Testort</ort><ortsteil/><land>D</land></anschrift>'
|
||||
.'<kommunikation><telefonprivat/><email>[email protected]</email><telefonmobil>0170123456</telefonmobil></kommunikation>'
|
||||
.'</rechnungsempfänger>'
|
||||
.'<zahlung idzahlungsart="2" bezeichnung="Überweisung" art="UEBERW"/>',
|
||||
$xml,
|
||||
);
|
||||
}
|
||||
|
||||
public function testThePurchaseDiffersOnlyInItsSaleType(): void
|
||||
{
|
||||
$builder = new VoucherSalePayloadBuilder();
|
||||
$dto = $this->createDto();
|
||||
|
||||
$inquiry = $builder->buildSalePayload($dto, Constants::BOOKING_TYPE_INQUIRY);
|
||||
$purchase = $builder->buildSalePayload($dto, Constants::BOOKING_TYPE_BOOKING);
|
||||
|
||||
self::assertSame('Buchung', $purchase['buchungsart']);
|
||||
self::assertSame(array_diff_key($inquiry, ['buchungsart' => true]), array_diff_key($purchase, ['buchungsart' => true]));
|
||||
}
|
||||
|
||||
public function testAnEmptyMobileIsLeftOut(): void
|
||||
{
|
||||
$dto = $this->createDto();
|
||||
$dto->mobile = ' ';
|
||||
|
||||
$payload = (new VoucherSalePayloadBuilder())->buildSalePayload($dto, Constants::BOOKING_TYPE_INQUIRY);
|
||||
|
||||
self::assertArrayNotHasKey('telefonmobil', $payload['rechnungsempfänger']['kommunikation']);
|
||||
}
|
||||
|
||||
public function testThePreviewPayloadCarriesTheVoucherOnly(): void
|
||||
{
|
||||
$payload = (new VoucherSalePayloadBuilder())->buildPreviewPayload($this->createDto());
|
||||
|
||||
self::assertSame([
|
||||
'idgutscheinvorlage' => 3,
|
||||
'kaeufer' => 'Probe Kaeufer',
|
||||
'empfaenger' => 'Probe Empfaenger',
|
||||
'kaufpreis' => 25,
|
||||
'text' => '',
|
||||
], $payload);
|
||||
}
|
||||
|
||||
public function testASaleWithoutATemplateIsALogicError(): void
|
||||
{
|
||||
$dto = $this->createDto();
|
||||
$dto->template = null;
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
|
||||
(new VoucherSalePayloadBuilder())->buildSalePayload($dto, Constants::BOOKING_TYPE_INQUIRY);
|
||||
}
|
||||
|
||||
private function createDto(): VoucherPurchaseDto
|
||||
{
|
||||
$dto = new VoucherPurchaseDto();
|
||||
$dto->template = new VoucherTemplate(3, 'K', 'Allgemein');
|
||||
$dto->paymentMethod = new VoucherPaymentMethod(2, 'Überweisung', 'UEBERW');
|
||||
$dto->from = 'Probe Kaeufer';
|
||||
$dto->to = 'Probe Empfaenger';
|
||||
$dto->amount = 25;
|
||||
$dto->salutation = VoucherPurchaseDto::SALUTATION_MR;
|
||||
$dto->lastName = 'Probe';
|
||||
$dto->firstName = 'Myep';
|
||||
$dto->address->street = 'Teststr. 1';
|
||||
$dto->address->postCode = '12345';
|
||||
$dto->address->city = 'Testort';
|
||||
$dto->email = '[email protected]';
|
||||
$dto->mobile = '0170123456';
|
||||
|
||||
return $dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\BusProNet\XmlParser;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\Exception\ResponseParserException;
|
||||
use App\BusProNet\Model\Notification;
|
||||
use App\BusProNet\Model\VoucherPaymentMethod;
|
||||
use App\BusProNet\Model\VoucherPreview;
|
||||
use App\BusProNet\Model\VoucherSaleResponse;
|
||||
use App\BusProNet\Model\VoucherTemplates;
|
||||
use App\BusProNet\XmlParser\ApiResponseParser;
|
||||
use App\BusProNet\XmlParser\TravelParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* The voucher purchase responses. Fixtures are the probes against the test system of 2026-10-01,
|
||||
* except the completed purchase, which follows the vendor documentation.
|
||||
*/
|
||||
class VoucherResponseParserTest extends TestCase
|
||||
{
|
||||
public function testTemplatesWithTheUndocumentedAmountBounds(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINVORLAGEN"></satz><gutscheinminbetrag>10</gutscheinminbetrag><gutscheinmaxbetrag>5000</gutscheinmaxbetrag><gutscheine><gutschein idgutscheinvorlage="3"><gutscheinart>K</gutscheinart><bezeichnung>Allgemein</bezeichnung><hinweis></hinweis></gutschein><gutschein idgutscheinvorlage="4"><gutscheinart>K</gutscheinart><bezeichnung>Ski</bezeichnung><hinweis></hinweis></gutschein><gutschein idgutscheinvorlage="5"><gutscheinart>K</gutscheinart><bezeichnung>Weihnachten</bezeichnung><hinweis></hinweis></gutschein></gutscheine></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_TEMPLATES, $xml);
|
||||
|
||||
self::assertInstanceOf(VoucherTemplates::class, $result);
|
||||
self::assertSame(10, $result->minAmount);
|
||||
self::assertSame(5000, $result->maxAmount);
|
||||
self::assertSame([3, 4, 5], array_map(static fn ($template) => $template->id, $result->templates));
|
||||
self::assertSame(['Allgemein', 'Ski', 'Weihnachten'], array_map(static fn ($template) => $template->label, $result->templates));
|
||||
self::assertSame('K', $result->templates[1]->type);
|
||||
self::assertNull($result->templates[1]->note);
|
||||
self::assertSame('Ski', $result->find(4)?->label);
|
||||
self::assertNull($result->find(999));
|
||||
}
|
||||
|
||||
public function testTemplatesWithoutAmountBoundsAreRejected(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINVORLAGEN"></satz><gutscheine><gutschein idgutscheinvorlage="3"><gutscheinart>K</gutscheinart><bezeichnung>Allgemein</bezeichnung></gutschein></gutscheine></ergebnis>';
|
||||
|
||||
$this->expectException(ResponseParserException::class);
|
||||
$this->expectExceptionMessage('amount bounds');
|
||||
|
||||
$this->parse(ApiClient::TYPE_VOUCHER_TEMPLATES, $xml);
|
||||
}
|
||||
|
||||
public function testPaymentMethods(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINZAHLUNGSARTEN"></satz><zahlungsarten><zahlungsart id="2" bezeichnung="Überweisung" art="UEBERW"></zahlungsart></zahlungsarten></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_PAYMENT_METHODS, $xml);
|
||||
|
||||
self::assertEquals([new VoucherPaymentMethod(2, 'Überweisung', 'UEBERW')], $result);
|
||||
self::assertTrue($result[0]->isTransfer());
|
||||
}
|
||||
|
||||
public function testPreviewIsDecodedFromItsDataUri(): void
|
||||
{
|
||||
$pdf = "%PDF-1.4\nprobe";
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINVORSCHAU"/><gutscheinpdf>data:application/pdf;base64,'.base64_encode($pdf).'</gutscheinpdf></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_PREVIEW, $xml);
|
||||
|
||||
self::assertInstanceOf(VoucherPreview::class, $result);
|
||||
self::assertSame($pdf, $result->pdf);
|
||||
}
|
||||
|
||||
public function testPreviewWithoutAPdfDataUriIsRejected(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINVORSCHAU"/><gutscheinpdf></gutscheinpdf></ergebnis>';
|
||||
|
||||
$this->expectException(ResponseParserException::class);
|
||||
|
||||
$this->parse(ApiClient::TYPE_VOUCHER_PREVIEW, $xml);
|
||||
}
|
||||
|
||||
public function testTheDryRunAnswersPossibleAndIssuesNothing(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINBUCHUNG"></satz><buchung>möglich</buchung></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_SALE, $xml);
|
||||
|
||||
self::assertInstanceOf(VoucherSaleResponse::class, $result);
|
||||
self::assertTrue($result->isInquiryValid());
|
||||
self::assertFalse($result->isSaleSuccessful());
|
||||
self::assertNull($result->voucherNumber);
|
||||
self::assertNull($result->redemptionCode);
|
||||
}
|
||||
|
||||
public function testACompletedPurchase(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINBUCHUNG"/><buchung>erfolgt</buchung><idgutschein>4711</idgutschein><gutscheinnummer>K-001234</gutscheinnummer><einloesecode>ABCD1234</einloesecode><preis>111,00</preis><rechnungsnummer>R-998877</rechnungsnummer><email>[email protected]</email></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_SALE, $xml);
|
||||
|
||||
self::assertInstanceOf(VoucherSaleResponse::class, $result);
|
||||
self::assertTrue($result->isSaleSuccessful());
|
||||
self::assertSame(4711, $result->voucherId);
|
||||
self::assertSame('K-001234', $result->voucherNumber);
|
||||
self::assertSame('ABCD1234', $result->redemptionCode);
|
||||
self::assertSame(111.0, $result->price);
|
||||
self::assertSame('R-998877', $result->invoiceNumber);
|
||||
self::assertSame('[email protected]', $result->email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing address data comes as 650, the code Notification::isError() treats as success.
|
||||
* The caller must take any HINWEIS as a rejection on this path.
|
||||
*/
|
||||
public function testAMissingAddressFieldIsAHinweis650(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="HINWEIS"><nr>650</nr><text>Fehlende Adress Daten. Bitte folgende Eingabefelder prüfen: Strasse</text></satz></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_SALE, $xml);
|
||||
|
||||
self::assertInstanceOf(Notification::class, $result);
|
||||
self::assertSame(650, $result->code);
|
||||
}
|
||||
|
||||
private function parse(string $type, string $xml): mixed
|
||||
{
|
||||
return (new ApiResponseParser($this->createStub(TravelParser::class)))->parseXmlString($type, $xml);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,12 @@ namespace App\Tests\Controller;
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\Controller\SecurityController;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\VoucherPurchaseDto;
|
||||
use App\Model\PurchaseFlow;
|
||||
use App\Service\BookingSessionManager;
|
||||
use App\Service\BookingSummaryAssembler;
|
||||
use App\Service\PurchaseFlowResolver;
|
||||
use App\Service\VoucherPurchaseSessionManager;
|
||||
use League\Bundle\OAuth2ServerBundle\Manager\ClientManagerInterface;
|
||||
use League\Bundle\OAuth2ServerBundle\Model\Client;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -98,6 +102,7 @@ class SecurityControllerTest extends TestCase
|
||||
->willReturn(new Client('EPHub', 'abc123', null));
|
||||
|
||||
$session = $this->createSession(self::AUTHORIZE_PATH);
|
||||
$session->set(BookingSessionManager::BOOKING_CREATE_KEY, $this->createBookingDto());
|
||||
$response = $controller->login(...$this->arguments($controller, self::AUTHORIZE_PATH, $session));
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
@@ -111,15 +116,65 @@ class SecurityControllerTest extends TestCase
|
||||
$controller->clientManager->expects(self::never())->method('find');
|
||||
|
||||
$session = $this->createSession(null);
|
||||
$session->set(BookingSessionManager::BOOKING_CREATE_KEY, $this->createBookingDto());
|
||||
$response = $controller->login(...$this->arguments($controller, null, $session));
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertSame('booking/create/authenticate.html.twig', $controller->renderedView);
|
||||
self::assertSame('/booking/create/step-1', $session->get('_security.main.target_path'));
|
||||
self::assertSame('/app_booking_create_step_1', $session->get('_security.main.target_path'));
|
||||
}
|
||||
|
||||
public function testAPendingVoucherPurchaseRendersTheVoucherLoginAndContinuesWithItsFirstStep(): void
|
||||
{
|
||||
$controller = $this->createController();
|
||||
$controller->clientManager->expects(self::never())->method('find');
|
||||
|
||||
$session = $this->createSession(null);
|
||||
$session->set(VoucherPurchaseSessionManager::SESSION_KEY, new VoucherPurchaseDto());
|
||||
$response = $controller->login(...$this->arguments($controller, null, $session));
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertSame('voucher/authenticate.html.twig', $controller->renderedView);
|
||||
self::assertSame('/app_voucher_step_1', $session->get('_security.main.target_path'));
|
||||
self::assertNull($controller->renderedParameters['travel_title']);
|
||||
}
|
||||
|
||||
public function testAPendingAuthorizationWinsOverAPendingVoucherPurchase(): void
|
||||
{
|
||||
$controller = $this->createController();
|
||||
$controller->clientManager->expects(self::once())->method('find')->willReturn(new Client('EPHub', 'abc123', null));
|
||||
|
||||
$session = $this->createSession(self::AUTHORIZE_PATH);
|
||||
$session->set(VoucherPurchaseSessionManager::SESSION_KEY, new VoucherPurchaseDto());
|
||||
$controller->login(...$this->arguments($controller, self::AUTHORIZE_PATH, $session));
|
||||
|
||||
self::assertSame('security/login_oauth2.html.twig', $controller->renderedView);
|
||||
self::assertSame(self::AUTHORIZE_PATH, $session->get('_security.main.target_path'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{AuthenticationUtils, Request, BookingSessionManager, BookingSummaryAssembler, ClientManagerInterface}
|
||||
* A booking and a voucher purchase can both be pending, e.g. from two tabs. The one the visitor
|
||||
* started last is the one they are signing in for.
|
||||
*/
|
||||
public function testTheLastStartedOfTwoPendingFlowsWins(): void
|
||||
{
|
||||
foreach ([PurchaseFlow::Voucher, PurchaseFlow::Booking] as $lastStarted) {
|
||||
$controller = $this->createController(bookingDto: $this->createBookingDto());
|
||||
$controller->clientManager->expects(self::never())->method('find');
|
||||
|
||||
$session = $this->createSession(null);
|
||||
$session->set(BookingSessionManager::BOOKING_CREATE_KEY, $this->createBookingDto());
|
||||
$session->set(VoucherPurchaseSessionManager::SESSION_KEY, new VoucherPurchaseDto());
|
||||
$session->set(PurchaseFlowResolver::LAST_STARTED_KEY, $lastStarted->value);
|
||||
$controller->login(...$this->arguments($controller, null, $session));
|
||||
|
||||
self::assertSame($lastStarted->authenticateTemplate(), $controller->renderedView);
|
||||
self::assertSame('/'.$lastStarted->firstStepRoute(), $session->get('_security.main.target_path'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{AuthenticationUtils, Request, BookingSessionManager, BookingSummaryAssembler, ClientManagerInterface, PurchaseFlowResolver}
|
||||
*/
|
||||
private function arguments(TestableSecurityController $controller, ?string $targetPath, ?Session $session = null): array
|
||||
{
|
||||
@@ -132,6 +187,7 @@ class SecurityControllerTest extends TestCase
|
||||
$controller->bookingSessionManager,
|
||||
$controller->summaryAssembler,
|
||||
$controller->clientManager,
|
||||
new PurchaseFlowResolver(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -203,6 +259,6 @@ final class TestableSecurityController extends SecurityController
|
||||
|
||||
protected function generateUrl(string $route, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
|
||||
{
|
||||
return '/booking/create/step-1';
|
||||
return '/'.$route;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Controller\Voucher;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\Exception\TimeoutException;
|
||||
use App\BusProNet\Model\Notification;
|
||||
use App\BusProNet\Model\VoucherPaymentMethod;
|
||||
use App\BusProNet\Model\VoucherSaleResponse;
|
||||
use App\BusProNet\Model\VoucherTemplate;
|
||||
use App\Controller\Voucher\Step3Controller;
|
||||
use App\Form\Model\VoucherPurchaseDto;
|
||||
use App\Service\VoucherPurchaseSessionManager;
|
||||
use App\Service\VoucherSaleRejectionMapper;
|
||||
use App\Service\VoucherThumbnailResolver;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
class Step3ControllerTest extends TestCase
|
||||
{
|
||||
private VoucherPurchaseSessionManager $sessionManager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->sessionManager = new VoucherPurchaseSessionManager();
|
||||
}
|
||||
|
||||
public function testAPassingDryRunOffersThePurchaseWithFreshConfirmations(): void
|
||||
{
|
||||
$request = $this->createRequest('GET', $dto = $this->createDto());
|
||||
$dto->termsAccepted = true;
|
||||
$dto->purchaseConfirmed = true;
|
||||
|
||||
$apiClient = $this->createMock(ApiClient::class);
|
||||
$apiClient->expects(self::once())->method('createVoucherSaleInquiry')->willReturn(new VoucherSaleResponse('möglich'));
|
||||
$apiClient->expects(self::never())->method('createVoucherSale');
|
||||
|
||||
$controller = $this->createController($apiClient);
|
||||
$controller->index($request);
|
||||
|
||||
self::assertTrue($controller->renderParameters['purchasable']);
|
||||
self::assertFalse($dto->termsAccepted);
|
||||
self::assertFalse($dto->purchaseConfirmed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 650 is what Notification::isError() calls a success. On this path it is a missing address field.
|
||||
*/
|
||||
public function testAnAddressComplaintSendsTheBuyerBackToStep2(): void
|
||||
{
|
||||
$request = $this->createRequest('GET', $this->createDto());
|
||||
|
||||
$apiClient = $this->createStub(ApiClient::class);
|
||||
$apiClient->method('createVoucherSaleInquiry')->willReturn(
|
||||
new Notification(650, 'Fehlende Adress Daten. Bitte folgende Eingabefelder prüfen: Strasse')
|
||||
);
|
||||
|
||||
$controller = $this->createController($apiClient);
|
||||
$response = $controller->index($request);
|
||||
|
||||
self::assertInstanceOf(RedirectResponse::class, $response);
|
||||
self::assertSame('/app_voucher_step_2', $response->getTargetUrl());
|
||||
self::assertSame(2, $this->sessionManager->find($request)?->currentStep);
|
||||
self::assertSame([['error', 'Fehlende Adress Daten. Bitte folgende Eingabefelder prüfen: Strasse']], $controller->flashes);
|
||||
}
|
||||
|
||||
public function testAnAmountComplaintSendsTheBuyerBackToStep1(): void
|
||||
{
|
||||
$request = $this->createRequest('GET', $this->createDto());
|
||||
|
||||
$apiClient = $this->createStub(ApiClient::class);
|
||||
$apiClient->method('createVoucherSaleInquiry')->willReturn(
|
||||
new Notification(608, 'Gutscheinbetrag darf nicht größer als 5.000,00 Euro sein')
|
||||
);
|
||||
|
||||
$response = $this->createController($apiClient)->index($request);
|
||||
|
||||
self::assertInstanceOf(RedirectResponse::class, $response);
|
||||
self::assertSame('/app_voucher_step_1', $response->getTargetUrl());
|
||||
}
|
||||
|
||||
public function testAComplaintNobodyCanFixKeepsTheOverviewWithoutABuyButton(): void
|
||||
{
|
||||
$request = $this->createRequest('GET', $this->createDto());
|
||||
|
||||
$apiClient = $this->createStub(ApiClient::class);
|
||||
$apiClient->method('createVoucherSaleInquiry')->willReturn(new Notification(619, 'Agentur-ID fehlt'));
|
||||
|
||||
$controller = $this->createController($apiClient);
|
||||
$controller->index($request);
|
||||
|
||||
self::assertFalse($controller->renderParameters['purchasable']);
|
||||
}
|
||||
|
||||
public function testAPurchaseClearsTheSessionAndKeepsTheRedemptionCodeOutOfTheLog(): void
|
||||
{
|
||||
$request = $this->createRequest('POST', $this->createDto());
|
||||
|
||||
$apiClient = $this->createMock(ApiClient::class);
|
||||
$apiClient->expects(self::never())->method('createVoucherSaleInquiry');
|
||||
$apiClient->expects(self::once())->method('createVoucherSale')->willReturn(new VoucherSaleResponse(
|
||||
status: 'erfolgt',
|
||||
voucherId: 4711,
|
||||
voucherNumber: 'K-001234',
|
||||
redemptionCode: 'ABCD1234',
|
||||
price: 111.0,
|
||||
invoiceNumber: 'R-998877',
|
||||
email: '[email protected]',
|
||||
));
|
||||
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects(self::once())->method('info')->with('Voucher successfully sold.', self::callback(
|
||||
static fn (array $context) => !\in_array('ABCD1234', $context, true) && 'K-001234' === $context['voucher_number']
|
||||
));
|
||||
|
||||
$controller = $this->createController($apiClient, $this->createSubmittedForm(), $logger);
|
||||
$response = $controller->index($request);
|
||||
|
||||
self::assertInstanceOf(RedirectResponse::class, $response);
|
||||
self::assertSame('/app_voucher_success', $response->getTargetUrl());
|
||||
self::assertNull($this->sessionManager->find($request));
|
||||
self::assertContains(['voucher_number', 'K-001234'], $controller->flashes);
|
||||
self::assertContains(['voucher_total', 111.0], $controller->flashes);
|
||||
self::assertNotContains('ABCD1234', array_column($controller->flashes, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* The first submit cleared the session before redirecting, so the second one finds nothing to buy.
|
||||
*/
|
||||
public function testASecondSubmitCannotBuyTwice(): void
|
||||
{
|
||||
$request = Request::create('/gutschein/uebersicht', 'POST');
|
||||
$request->setSession(new Session(new MockArraySessionStorage()));
|
||||
|
||||
$apiClient = $this->createMock(ApiClient::class);
|
||||
$apiClient->expects(self::never())->method('createVoucherSale');
|
||||
|
||||
$response = $this->createController($apiClient, $this->createSubmittedForm())->index($request);
|
||||
|
||||
self::assertInstanceOf(RedirectResponse::class, $response);
|
||||
self::assertSame('/app_voucher_error', $response->getTargetUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* A timeout leaves it open whether BusPro issued the voucher, so no blind retry is offered.
|
||||
*/
|
||||
public function testATimeoutDuringThePurchaseOffersNoRetry(): void
|
||||
{
|
||||
$request = $this->createRequest('POST', $this->createDto());
|
||||
|
||||
$apiClient = $this->createStub(ApiClient::class);
|
||||
$apiClient->method('createVoucherSale')->willThrowException(new TimeoutException('Stream timeout'));
|
||||
|
||||
$controller = $this->createController($apiClient, $this->createSubmittedForm());
|
||||
$controller->index($request);
|
||||
|
||||
self::assertFalse($controller->renderParameters['purchasable']);
|
||||
self::assertNotNull($this->sessionManager->find($request));
|
||||
self::assertStringContainsString('Kundenservice', $controller->flashes[0][1]);
|
||||
}
|
||||
|
||||
private function createRequest(string $method, VoucherPurchaseDto $dto): Request
|
||||
{
|
||||
$request = Request::create('/gutschein/uebersicht', $method);
|
||||
$request->setSession(new Session(new MockArraySessionStorage()));
|
||||
$this->sessionManager->save($request, $dto);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function createDto(): VoucherPurchaseDto
|
||||
{
|
||||
$dto = new VoucherPurchaseDto();
|
||||
$dto->currentStep = 3;
|
||||
$dto->template = new VoucherTemplate(3, 'K', 'Allgemein');
|
||||
$dto->paymentMethod = new VoucherPaymentMethod(2, 'Überweisung', 'UEBERW');
|
||||
$dto->amount = 111;
|
||||
|
||||
return $dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormInterface<VoucherPurchaseDto>
|
||||
*/
|
||||
private function createSubmittedForm(): FormInterface
|
||||
{
|
||||
$form = $this->createStub(FormInterface::class);
|
||||
$form->method('isSubmitted')->willReturn(true);
|
||||
$form->method('isValid')->willReturn(true);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormInterface<VoucherPurchaseDto>|null $form
|
||||
*/
|
||||
private function createController(ApiClient $apiClient, ?FormInterface $form = null, ?LoggerInterface $logger = null): TestableStep3Controller
|
||||
{
|
||||
return new TestableStep3Controller(
|
||||
$this->sessionManager,
|
||||
$this->createStub(VoucherThumbnailResolver::class),
|
||||
$apiClient,
|
||||
new VoucherSaleRejectionMapper(),
|
||||
$logger ?? new NullLogger(),
|
||||
$form ?? $this->createStub(FormInterface::class),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class TestableStep3Controller extends Step3Controller
|
||||
{
|
||||
/**
|
||||
* @var list<array{string, mixed}>
|
||||
*/
|
||||
public array $flashes = [];
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public array $renderParameters = [];
|
||||
|
||||
/**
|
||||
* @param FormInterface<VoucherPurchaseDto> $form
|
||||
*/
|
||||
public function __construct(
|
||||
VoucherPurchaseSessionManager $sessionManager,
|
||||
VoucherThumbnailResolver $thumbnailResolver,
|
||||
ApiClient $apiClient,
|
||||
VoucherSaleRejectionMapper $rejectionMapper,
|
||||
LoggerInterface $logger,
|
||||
private readonly FormInterface $form,
|
||||
) {
|
||||
parent::__construct($sessionManager, $thumbnailResolver, $apiClient, $rejectionMapper, $logger);
|
||||
}
|
||||
|
||||
protected function getUser(): ?UserInterface
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormInterface<mixed>
|
||||
*/
|
||||
protected function createForm(string $type, mixed $data = null, array $options = []): FormInterface
|
||||
{
|
||||
return $this->form;
|
||||
}
|
||||
|
||||
protected function addFlash(string $type, mixed $message): void
|
||||
{
|
||||
$this->flashes[] = [$type, $message];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $parameters
|
||||
*/
|
||||
protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse
|
||||
{
|
||||
return new RedirectResponse('/'.$route, $status);
|
||||
}
|
||||
|
||||
protected function generateUrl(string $route, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
|
||||
{
|
||||
return '/'.$route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $parameters
|
||||
*/
|
||||
protected function render(string $view, array $parameters = [], ?Response $response = null): Response
|
||||
{
|
||||
$this->renderParameters = $parameters;
|
||||
|
||||
return new Response('');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Form\Model;
|
||||
|
||||
use App\BusProNet\Model\PersonalData;
|
||||
use App\BusProNet\Model\VoucherTemplate;
|
||||
use App\Form\Model\VoucherPurchaseDto;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Validator\ConstraintViolationListInterface;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
|
||||
class VoucherPurchaseDtoTest extends TestCase
|
||||
{
|
||||
private const array STEP_1 = ['voucher_step_1'];
|
||||
private const array STEP_2 = ['voucher_step_2', 'personal_data'];
|
||||
private const array STEP_3 = ['voucher_step_3'];
|
||||
|
||||
public function testACompleteVoucherPassesStep1(): void
|
||||
{
|
||||
self::assertCount(0, $this->validate($this->createDto(), self::STEP_1));
|
||||
}
|
||||
|
||||
public function testTheBoundsAreInclusiveAndComeFromBusPro(): void
|
||||
{
|
||||
$dto = $this->createDto();
|
||||
|
||||
foreach ([10 => 0, 5000 => 0, 9 => 1, 5001 => 1] as $amount => $violations) {
|
||||
$dto->amount = $amount;
|
||||
$result = $this->validate($dto, self::STEP_1);
|
||||
|
||||
self::assertCount($violations, $result, sprintf('amount %d', $amount));
|
||||
if (1 === $violations) {
|
||||
self::assertSame('amount', $result->get(0)->getPropertyPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testStep1RequiresDesignNamesAndValue(): void
|
||||
{
|
||||
$dto = $this->createDto();
|
||||
$dto->template = null;
|
||||
$dto->from = '';
|
||||
$dto->to = null;
|
||||
$dto->amount = null;
|
||||
|
||||
self::assertSame(
|
||||
['template', 'from', 'to', 'amount'],
|
||||
$this->propertyPaths($this->validate($dto, self::STEP_1)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The mandatory fields of the live voucher shop: no phone, no gender, no salutation.
|
||||
*/
|
||||
public function testStep2RequiresWhatTheLiveShopRequires(): void
|
||||
{
|
||||
$dto = new VoucherPurchaseDto();
|
||||
$dto->address->country = null;
|
||||
|
||||
self::assertEqualsCanonicalizing(
|
||||
['lastName', 'firstName', 'address.street', 'address.postCode', 'address.city', 'address.country', 'email'],
|
||||
$this->propertyPaths($this->validate($dto, self::STEP_2)),
|
||||
);
|
||||
}
|
||||
|
||||
public function testACompleteBuyerPassesStep2(): void
|
||||
{
|
||||
self::assertCount(0, $this->validate($this->createDto(), self::STEP_2));
|
||||
}
|
||||
|
||||
public function testTheCountryDefaultsToGermany(): void
|
||||
{
|
||||
self::assertSame('D', (new VoucherPurchaseDto())->address->country);
|
||||
}
|
||||
|
||||
public function testStep2RejectsAnInvalidEmailAndSalutation(): void
|
||||
{
|
||||
$dto = $this->createDto();
|
||||
$dto->email = 'kein-mail';
|
||||
$dto->salutation = 'Divers';
|
||||
|
||||
self::assertEqualsCanonicalizing(['email', 'salutation'], $this->propertyPaths($this->validate($dto, self::STEP_2)));
|
||||
}
|
||||
|
||||
public function testStep3RequiresBothConfirmations(): void
|
||||
{
|
||||
$dto = $this->createDto();
|
||||
|
||||
self::assertSame(['termsAccepted', 'purchaseConfirmed'], $this->propertyPaths($this->validate($dto, self::STEP_3)));
|
||||
|
||||
$dto->termsAccepted = true;
|
||||
$dto->purchaseConfirmed = true;
|
||||
self::assertCount(0, $this->validate($dto, self::STEP_3));
|
||||
}
|
||||
|
||||
public function testPrefillTakesTheLoginEmailAndKeepsAnUnknownSalutationOut(): void
|
||||
{
|
||||
$personalData = new PersonalData();
|
||||
$personalData->name = 'Muster';
|
||||
$personalData->firstName = 'Erika';
|
||||
$personalData->salutation = 'Familie';
|
||||
$personalData->address->street = ' Hauptstr. 1 ';
|
||||
$personalData->address->postCode = '12345';
|
||||
$personalData->address->city = 'Musterstadt';
|
||||
$personalData->address->country = 'A';
|
||||
$personalData->communication->email = '[email protected]';
|
||||
$personalData->communication->mobile = '0170';
|
||||
|
||||
$dto = new VoucherPurchaseDto();
|
||||
$dto->prefillBuyer($personalData, '[email protected]');
|
||||
|
||||
self::assertSame('Muster', $dto->lastName);
|
||||
self::assertSame('Erika', $dto->firstName);
|
||||
self::assertNull($dto->salutation);
|
||||
self::assertSame('Hauptstr. 1', $dto->address->street);
|
||||
self::assertSame('A', $dto->address->country);
|
||||
self::assertSame('[email protected]', $dto->email);
|
||||
self::assertSame('0170', $dto->mobile);
|
||||
self::assertTrue($dto->hasBuyerData());
|
||||
self::assertNotSame($personalData->address, $dto->address);
|
||||
}
|
||||
|
||||
private function createDto(): VoucherPurchaseDto
|
||||
{
|
||||
$dto = new VoucherPurchaseDto();
|
||||
$dto->minAmount = 10;
|
||||
$dto->maxAmount = 5000;
|
||||
$dto->template = new VoucherTemplate(3, 'K', 'Allgemein');
|
||||
$dto->from = 'Oma';
|
||||
$dto->to = 'Max';
|
||||
$dto->amount = 111;
|
||||
$dto->lastName = 'Muster';
|
||||
$dto->firstName = 'Erika';
|
||||
$dto->address->street = 'Hauptstr. 1';
|
||||
$dto->address->postCode = '12345';
|
||||
$dto->address->city = 'Musterstadt';
|
||||
$dto->email = '[email protected]';
|
||||
|
||||
return $dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $groups
|
||||
*/
|
||||
private function validate(VoucherPurchaseDto $dto, array $groups): ConstraintViolationListInterface
|
||||
{
|
||||
return Validation::createValidatorBuilder()
|
||||
->enableAttributeMapping()
|
||||
->getValidator()
|
||||
->validate($dto, null, $groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function propertyPaths(ConstraintViolationListInterface $violations): array
|
||||
{
|
||||
$paths = [];
|
||||
foreach ($violations as $violation) {
|
||||
$paths[] = $violation->getPropertyPath();
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,7 @@ class ParticipantDataPrefillerTest extends TestCase
|
||||
|
||||
$this->logger->expects($this->once())
|
||||
->method('error')
|
||||
->with('Exception during applicant prepopulation', [
|
||||
->with('Exception during personal data prepopulation', [
|
||||
'error' => 'Decryption failed',
|
||||
'email' => '[email protected]',
|
||||
]);
|
||||
@@ -221,7 +221,7 @@ class ParticipantDataPrefillerTest extends TestCase
|
||||
|
||||
$this->logger->expects($this->once())
|
||||
->method('error')
|
||||
->with('Exception during applicant prepopulation', [
|
||||
->with('Exception during personal data prepopulation', [
|
||||
'error' => 'API connection failed',
|
||||
'email' => '[email protected]',
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\BusProNet\Model\Notification;
|
||||
use App\Service\VoucherSaleRejectionMapper;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class VoucherSaleRejectionMapperTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @return iterable<string, array{int, string, int}>
|
||||
*/
|
||||
public static function probedRejections(): iterable
|
||||
{
|
||||
yield 'unknown template' => [602, 'Gutscheine mit ID 999 nicht gefunden', 1];
|
||||
yield 'recipient missing' => [605, 'Empfängername fehlt', 1];
|
||||
yield 'below minimum' => [607, 'Gutscheinbetrag darf nicht kleiner als 10,00 Euro sein', 1];
|
||||
yield 'above maximum' => [608, 'Gutscheinbetrag darf nicht größer als 5.000,00 Euro sein', 1];
|
||||
yield 'capacity' => [616, 'Kapazität nicht ausreichend', 1];
|
||||
yield 'email missing' => [650, 'Fehlende Adress Daten. Bitte folgende Eingabefelder prüfen: Mail-Adresse', 2];
|
||||
yield 'street missing' => [650, 'Fehlende Adress Daten. Bitte folgende Eingabefelder prüfen: Strasse', 2];
|
||||
yield 'payment method missing' => [618, 'Zahlungsart fehlt', 3];
|
||||
yield 'agency' => [619, 'Agentur-ID fehlt', 3];
|
||||
}
|
||||
|
||||
#[DataProvider('probedRejections')]
|
||||
public function testTheRejectionGoesBackToTheStepThatOwnsIt(int $code, string $message, int $step): void
|
||||
{
|
||||
self::assertSame($step, (new VoucherSaleRejectionMapper())->stepFor(new Notification($code, $message)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\Model\Notification;
|
||||
use App\BusProNet\Model\VoucherPaymentMethod;
|
||||
use App\BusProNet\Model\VoucherTemplate;
|
||||
use App\BusProNet\Model\VoucherTemplates;
|
||||
use App\Exception\VoucherShopUnavailableException;
|
||||
use App\Service\VoucherTemplateProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
|
||||
|
||||
class VoucherTemplateProviderTest extends TestCase
|
||||
{
|
||||
public function testTemplatesAreCached(): void
|
||||
{
|
||||
$templates = new VoucherTemplates(10, 5000, [new VoucherTemplate(3, 'K', 'Allgemein')]);
|
||||
$apiClient = $this->createMock(ApiClient::class);
|
||||
$apiClient->expects(self::once())->method('getVoucherTemplates')->willReturn($templates);
|
||||
|
||||
$provider = $this->createProvider($apiClient);
|
||||
|
||||
self::assertEquals($templates, $provider->getTemplates());
|
||||
self::assertEquals($templates, $provider->getTemplates());
|
||||
}
|
||||
|
||||
public function testARejectionIsNotCached(): void
|
||||
{
|
||||
$templates = new VoucherTemplates(10, 5000, [new VoucherTemplate(3, 'K', 'Allgemein')]);
|
||||
$apiClient = $this->createMock(ApiClient::class);
|
||||
$apiClient->expects(self::exactly(2))->method('getVoucherTemplates')
|
||||
->willReturnOnConsecutiveCalls(new Notification(104, 'User fehlerhaft'), $templates);
|
||||
|
||||
$provider = $this->createProvider($apiClient);
|
||||
|
||||
try {
|
||||
$provider->getTemplates();
|
||||
self::fail('Expected a VoucherShopUnavailableException');
|
||||
} catch (VoucherShopUnavailableException) {
|
||||
}
|
||||
|
||||
self::assertEquals($templates, $provider->getTemplates());
|
||||
}
|
||||
|
||||
public function testNoTemplatesOnSaleMeansNoShop(): void
|
||||
{
|
||||
$apiClient = $this->createStub(ApiClient::class);
|
||||
$apiClient->method('getVoucherTemplates')->willReturn(new VoucherTemplates(10, 5000, []));
|
||||
|
||||
$this->expectException(VoucherShopUnavailableException::class);
|
||||
|
||||
$this->createProvider($apiClient)->getTemplates();
|
||||
}
|
||||
|
||||
public function testThePaymentMethodIsTransfer(): void
|
||||
{
|
||||
$apiClient = $this->createStub(ApiClient::class);
|
||||
$apiClient->method('getVoucherPaymentMethods')->willReturn([
|
||||
new VoucherPaymentMethod(1, 'Lastschrift', 'LAST'),
|
||||
new VoucherPaymentMethod(2, 'Überweisung', 'UEBERW'),
|
||||
]);
|
||||
|
||||
self::assertSame(2, $this->createProvider($apiClient)->getPaymentMethod()->id);
|
||||
}
|
||||
|
||||
public function testWithoutTransferThereIsNoShop(): void
|
||||
{
|
||||
$apiClient = $this->createStub(ApiClient::class);
|
||||
$apiClient->method('getVoucherPaymentMethods')->willReturn([new VoucherPaymentMethod(1, 'Lastschrift', 'LAST')]);
|
||||
|
||||
$this->expectException(VoucherShopUnavailableException::class);
|
||||
|
||||
$this->createProvider($apiClient)->getPaymentMethod();
|
||||
}
|
||||
|
||||
private function createProvider(ApiClient $apiClient): VoucherTemplateProvider
|
||||
{
|
||||
return new VoucherTemplateProvider($apiClient, new TagAwareAdapter(new ArrayAdapter()), new NullLogger());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\BusProNet\Model\VoucherTemplate;
|
||||
use App\Service\VoucherThumbnailResolver;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class VoucherThumbnailResolverTest extends TestCase
|
||||
{
|
||||
private string $manifestPath;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->manifestPath = tempnam(sys_get_temp_dir(), 'manifest');
|
||||
file_put_contents($this->manifestPath, json_encode([
|
||||
'build/images/vouchers/muster_3.jpg' => '/build/images/vouchers/muster_3.0a1b2c3d.jpg',
|
||||
]));
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
@unlink($this->manifestPath);
|
||||
}
|
||||
|
||||
public function testAThumbnailIsFoundByTheTemplateId(): void
|
||||
{
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects(self::never())->method('warning');
|
||||
|
||||
$resolver = new VoucherThumbnailResolver($this->manifestPath, $logger);
|
||||
|
||||
self::assertSame('build/images/vouchers/muster_3.jpg', $resolver->resolve(new VoucherTemplate(3, 'K', 'Allgemein')));
|
||||
}
|
||||
|
||||
public function testANewDesignWithoutAThumbnailRendersWithoutOneAndIsReported(): void
|
||||
{
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects(self::once())->method('warning')->with('No thumbnail for voucher template', self::callback(
|
||||
static fn (array $context) => 6 === $context['template_id'] && 'assets/vouchers/muster_6.jpg' === $context['expected_source']
|
||||
));
|
||||
|
||||
$resolver = new VoucherThumbnailResolver($this->manifestPath, $logger);
|
||||
|
||||
self::assertSame(
|
||||
[3 => 'build/images/vouchers/muster_3.jpg', 6 => null],
|
||||
$resolver->resolveAll([new VoucherTemplate(3, 'K', 'Allgemein'), new VoucherTemplate(6, 'K', 'Ostern')]),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user