feat: voucher purchase flow

This commit is contained in:
fromme committed 2026-10-01 15:03:27 +02:00
1 parent a628e96d06
commit f3dd122678
70 files changed
+3672 -122

No files matched your search

@@ -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')]),
);
}
}