feat: authenticated booking

This commit is contained in:
Björn Fromme
2025-10-24 17:07:08 +02:00
parent 647a25a78f
commit b63804df88
13 changed files with 866 additions and 50 deletions
@@ -0,0 +1,307 @@
<?php
declare(strict_types=1);
namespace App\Tests\Service;
use App\BusProNet\ApiClient;
use App\BusProNet\Model\Address;
use App\BusProNet\Model\Communication;
use App\BusProNet\Model\Notification;
use App\BusProNet\Model\PersonalData;
use App\Entity\User;
use App\Form\Model\ParticipantDto;
use App\Security\Crypt;
use App\Service\ParticipantPrepopulationService;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class ParticipantPrepopulationServiceTest extends TestCase
{
private ApiClient $apiClient;
private Crypt $crypt;
private LoggerInterface $logger;
private ParticipantPrepopulationService $service;
protected function setUp(): void
{
$this->apiClient = $this->createMock(ApiClient::class);
$this->crypt = $this->createMock(Crypt::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new ParticipantPrepopulationService(
$this->apiClient,
$this->crypt,
$this->logger
);
}
public function testPrepopulateApplicantWithCompletePersonalData(): void
{
$user = $this->createUser('[email protected]', 'encrypted_password');
$applicant = new ParticipantDto();
$applicant->index = 0;
$personalData = $this->createCompletePersonalData();
$this->crypt->expects($this->once())
->method('decrypt')
->with('encrypted_password')
->willReturn('decrypted_password');
$this->apiClient->expects($this->once())
->method('getPersonalData')
->with('[email protected]', 'decrypted_password')
->willReturn($personalData);
$this->logger->expects($this->once())
->method('info')
->with('Prepopulated applicant data from user profile', ['email' => '[email protected]']);
$result = $this->service->prepopulateApplicantFromUser($user, $applicant);
// BPN API IDs for linking to existing records
$this->assertSame(12345, $result->addressId);
$this->assertSame(67890, $result->personId);
$this->assertSame('John', $result->firstName);
$this->assertSame('Doe', $result->lastName);
$this->assertSame('Dr.', $result->title);
$this->assertSame('M', $result->gender);
$this->assertSame('DE', $result->nationality);
$this->assertSame('[email protected]', $result->email);
$this->assertSame('+49123456789', $result->mobile);
$this->assertSame('180', $result->height);
$this->assertSame('75', $result->weight);
$this->assertSame('42', $result->shoeSize);
$this->assertSame('No smoking room please', $result->remarksRoom);
$this->assertSame('AB-CD-1234', $result->licensePlate);
$this->assertNotNull($result->dateOfBirth);
$this->assertSame('1990-05-15', $result->dateOfBirth->format('Y-m-d'));
$this->assertNotNull($result->address);
$this->assertSame('Main Street 123', $result->address->street);
$this->assertSame('12345', $result->address->postCode);
$this->assertSame('Berlin', $result->address->city);
$this->assertSame('DE', $result->address->country);
$this->assertSame('Mitte', $result->address->district);
}
public function testPrepopulateApplicantWithPartialPersonalData(): void
{
$user = $this->createUser('[email protected]', 'encrypted_password');
$applicant = new ParticipantDto();
$applicant->index = 0;
$personalData = $this->createPartialPersonalData();
$this->crypt->expects($this->once())
->method('decrypt')
->willReturn('decrypted_password');
$this->apiClient->expects($this->once())
->method('getPersonalData')
->willReturn($personalData);
$this->logger->expects($this->once())
->method('info');
$result = $this->service->prepopulateApplicantFromUser($user, $applicant);
// Required fields should be populated
$this->assertSame('Jane', $result->firstName);
$this->assertSame('Smith', $result->lastName);
$this->assertSame('[email protected]', $result->email);
// Optional fields should be null
$this->assertNull($result->title);
$this->assertNull($result->gender);
$this->assertNull($result->nationality);
$this->assertNull($result->mobile);
$this->assertNull($result->height);
$this->assertNull($result->weight);
$this->assertNull($result->shoeSize);
$this->assertNull($result->remarksRoom);
$this->assertNull($result->licensePlate);
}
public function testPrepopulateApplicantWithApiNotificationError(): void
{
$user = $this->createUser('[email protected]', 'encrypted_password');
$applicant = new ParticipantDto();
$applicant->index = 0;
$notification = new Notification(401, 'Authentication failed');
$this->crypt->expects($this->once())
->method('decrypt')
->willReturn('decrypted_password');
$this->apiClient->expects($this->once())
->method('getPersonalData')
->willReturn($notification);
$this->logger->expects($this->once())
->method('warning')
->with('Failed to fetch personal data for prepopulation', [
'email' => '[email protected]',
'code' => 401,
'message' => 'Authentication failed',
]);
$this->logger->expects($this->never())
->method('info');
$result = $this->service->prepopulateApplicantFromUser($user, $applicant);
// Should return unchanged applicant
$this->assertNull($result->firstName);
$this->assertNull($result->lastName);
$this->assertNull($result->email);
}
public function testPrepopulateApplicantWithDecryptionException(): void
{
$user = $this->createUser('[email protected]', 'encrypted_password');
$applicant = new ParticipantDto();
$applicant->index = 0;
$this->crypt->expects($this->once())
->method('decrypt')
->willThrowException(new \RuntimeException('Decryption failed'));
$this->apiClient->expects($this->never())
->method('getPersonalData');
$this->logger->expects($this->once())
->method('error')
->with('Exception during applicant prepopulation', [
'error' => 'Decryption failed',
'email' => '[email protected]',
]);
$result = $this->service->prepopulateApplicantFromUser($user, $applicant);
// Should return unchanged applicant
$this->assertNull($result->firstName);
$this->assertNull($result->lastName);
$this->assertNull($result->email);
}
public function testPrepopulateApplicantWithApiException(): void
{
$user = $this->createUser('[email protected]', 'encrypted_password');
$applicant = new ParticipantDto();
$applicant->index = 0;
$this->crypt->expects($this->once())
->method('decrypt')
->willReturn('decrypted_password');
$this->apiClient->expects($this->once())
->method('getPersonalData')
->willThrowException(new \RuntimeException('API connection failed'));
$this->logger->expects($this->once())
->method('error')
->with('Exception during applicant prepopulation', [
'error' => 'API connection failed',
'email' => '[email protected]',
]);
$result = $this->service->prepopulateApplicantFromUser($user, $applicant);
// Should return unchanged applicant
$this->assertNull($result->firstName);
$this->assertNull($result->lastName);
$this->assertNull($result->email);
}
public function testPrepopulatePreservesBookingSpecificFields(): void
{
$user = $this->createUser('[email protected]', 'encrypted_password');
$applicant = new ParticipantDto();
$applicant->index = 0;
$applicant->assignedRoomId = 42;
$applicant->bulkInsuranceBooking = true;
$personalData = $this->createCompletePersonalData();
$this->crypt->expects($this->once())
->method('decrypt')
->willReturn('decrypted_password');
$this->apiClient->expects($this->once())
->method('getPersonalData')
->willReturn($personalData);
$result = $this->service->prepopulateApplicantFromUser($user, $applicant);
// Personal data should be updated
$this->assertSame('John', $result->firstName);
$this->assertSame('Doe', $result->lastName);
// Booking-specific fields should be preserved
$this->assertSame(42, $result->assignedRoomId);
$this->assertTrue($result->bulkInsuranceBooking);
}
private function createUser(string $email, string $encryptedPassword): User
{
$user = new User($email);
$user->setPassword($encryptedPassword);
return $user;
}
private function createCompletePersonalData(): PersonalData
{
$personalData = new PersonalData();
$personalData->addressId = 12345;
$personalData->personId = 67890;
$personalData->firstName = 'John';
$personalData->name = 'Doe';
$personalData->title = 'Dr.';
$personalData->gender = 'M';
$personalData->nationality = 'DE';
$personalData->dateOfBirth = new \DateTimeImmutable('1990-05-15');
$personalData->height = '180';
$personalData->weight = '75';
$personalData->shoeSize = '42';
$personalData->remarksRoom = 'No smoking room please';
$personalData->licensePlate = 'AB-CD-1234';
$personalData->communication = new Communication();
$personalData->communication->email = '[email protected]';
$personalData->communication->mobile = '+49123456789';
$personalData->address = new Address();
$personalData->address->street = 'Main Street 123';
$personalData->address->postCode = '12345';
$personalData->address->city = 'Berlin';
$personalData->address->country = 'DE';
$personalData->address->district = 'Mitte';
return $personalData;
}
private function createPartialPersonalData(): PersonalData
{
$personalData = new PersonalData();
$personalData->firstName = 'Jane';
$personalData->name = 'Smith';
$personalData->dateOfBirth = new \DateTimeImmutable('1995-03-20');
$personalData->communication = new Communication();
$personalData->communication->email = '[email protected]';
$personalData->address = new Address();
$personalData->address->street = 'Second Street 45';
$personalData->address->postCode = '54321';
$personalData->address->city = 'Munich';
$personalData->address->country = 'DE';
return $personalData;
}
}