From 24d02c0776723a277001684b59c9914241b94209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Tue, 3 Mar 2026 08:53:25 +0100 Subject: [PATCH] fix: sync all teamer data on login --- src/BusProNet/UserDataHandler.php | 62 +++++- tests/BusProNet/UserDataHandlerTest.php | 272 ++++++++++++++++++++++++ 2 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 tests/BusProNet/UserDataHandlerTest.php diff --git a/src/BusProNet/UserDataHandler.php b/src/BusProNet/UserDataHandler.php index 88c722c..70f923a 100644 --- a/src/BusProNet/UserDataHandler.php +++ b/src/BusProNet/UserDataHandler.php @@ -44,10 +44,62 @@ class UserDataHandler // Check if user is already present in local database $repository = $this->entityManager->getRepository(User::class); - return $repository->findOneBy([ + $user = $repository->findOneBy([ 'busProAddressId' => $profileResponse->getAddressId(), 'busProPersonId' => $profileResponse->getPersonId(), ]); + + if (null !== $user) { + return $user; + } + + $email = $profileResponse->getCommunication()?->getEmail(); + if (true === empty($email)) { + return null; + } + + $users = $repository->findBy([ + 'email' => $email, + ]); + + if (1 !== count($users)) { + if (1 < count($users)) { + $this->logger->warning('Unable to match local user by email: multiple users found', [ + 'email' => $email, + 'count' => count($users), + ]); + } + + return null; + } + + /** @var User $user */ + $user = $users[0]; + + $addressId = $profileResponse->getAddressId(); + $personId = $profileResponse->getPersonId(); + if (null === $addressId || null === $personId) { + $this->logger->warning('Skip BusPro ID refresh: profile response missing IDs', [ + 'user_id' => $user->getId(), + 'user_email' => $user->getEmail(), + 'bus_pro_address_id' => $addressId, + 'bus_pro_person_id' => $personId, + ]); + + return $user; + } + + $user + ->setBusProAddressId($addressId) + ->setBusProPersonId($personId) + ; + + $this->logger->info('Refresh BusPro IDs for existing local user', [ + 'user_id' => $user->getId(), + 'user_email' => $user->getEmail(), + ]); + + return $user; } public function createLocalUser( @@ -98,6 +150,8 @@ class UserDataHandler array $hotelCodes = [], ): void { $user + ->setFirstName($profileResponse->getFirstName()) + ->setLastName($profileResponse->getName()) ->setEmail($profileResponse->getCommunication()->getEmail()) ->setRoles($roles) ->setHotelCodes($hotelCodes) @@ -114,6 +168,12 @@ class UserDataHandler $teamer = $user->getTeamer(); } $teamer + ->setFirstName($profileResponse->getFirstName()) + ->setLastName($profileResponse->getName()) + ->setAcademicTitle($profileResponse->getTitle()) + ->setSalutation($profileResponse->getSalutation()) + ->setGender($profileResponse->getGender()) + ->setDateOfBirth($profileResponse->getDateOfBirth()) ->setAddress($address) ->setCommunication($communication) ->setCrmSelections($crmSelections) diff --git a/tests/BusProNet/UserDataHandlerTest.php b/tests/BusProNet/UserDataHandlerTest.php new file mode 100644 index 0000000..7009c58 --- /dev/null +++ b/tests/BusProNet/UserDataHandlerTest.php @@ -0,0 +1,272 @@ +entityManager = $this->createMock(EntityManagerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); + } + + public function testUpdateLocalUserSyncsUserAndTeamerDataFromBusPro(): void + { + $user = (new User()) + ->setFirstName('Old') + ->setLastName('Name') + ->setEmail('old@example.com') + ->setBusProAddressId(1) + ->setBusProPersonId(2) + ; + + $teamer = (new Teamer()) + ->setFirstName('OldTeamer') + ->setLastName('OldLastname') + ->setAcademicTitle('Dr.') + ->setSalutation('Herr') + ->setGender('M') + ->setDateOfBirth(new \DateTimeImmutable('1990-01-01')) + ->setAddress((new Address())->setStreet('Old Street')->setPostCode('11111')->setCity('Old City')->setCountry('DE')) + ->setCommunication((new Communication())->setPhone('123')->setMobile('456')->setEmail('old@example.com')) + ; + $user->setTeamer($teamer); + + $profileResponse = $this->createProfileResponse(); + + $this->entityManager + ->expects($this->once()) + ->method('flush'); + + $handler = new UserDataHandler($this->entityManager, $this->logger); + $handler->updateLocalUser( + $user, + $profileResponse, + ['ROLE_TEAMER'], + true, + ['team' => ['selected' => true]], + ['ABC'], + ); + + $this->assertSame('New', $user->getFirstName()); + $this->assertSame('Lastname', $user->getLastName()); + $this->assertSame('new@example.com', $user->getEmail()); + $this->assertSame(['ABC'], $user->getHotelCodes()); + $this->assertTrue($user->hasRole('ROLE_TEAMER')); + + $this->assertSame('New', $teamer->getFirstName()); + $this->assertSame('Lastname', $teamer->getLastName()); + $this->assertSame('Prof.', $teamer->getAcademicTitle()); + $this->assertSame('Frau', $teamer->getSalutation()); + $this->assertSame('F', $teamer->getGender()); + $this->assertEquals(new \DateTimeImmutable('1995-12-24'), $teamer->getDateOfBirth()); + $this->assertSame('New Street 123', $teamer->getAddress()?->getStreet()); + $this->assertSame('54321', $teamer->getAddress()?->getPostCode()); + $this->assertSame('New City', $teamer->getAddress()?->getCity()); + $this->assertSame('AT', $teamer->getAddress()?->getCountry()); + $this->assertSame('999', $teamer->getCommunication()?->getPhone()); + $this->assertSame('888', $teamer->getCommunication()?->getMobile()); + $this->assertSame('new@example.com', $teamer->getCommunication()?->getEmail()); + $this->assertSame(['team' => ['selected' => true]], $teamer->getCrmSelections()); + } + + public function testFindLocalUserFallsBackToUniqueEmailAndRefreshesBusProIds(): void + { + $user = (new User()) + ->setFirstName('First') + ->setLastName('Last') + ->setEmail('new@example.com') + ->setBusProAddressId(1) + ->setBusProPersonId(2) + ; + + $profileResponse = $this->createProfileResponse(); + + $repository = $this->createMock(ObjectRepository::class); + $repository + ->expects($this->once()) + ->method('findOneBy') + ->with([ + 'busProAddressId' => 200, + 'busProPersonId' => 100, + ]) + ->willReturn(null); + + $repository + ->expects($this->once()) + ->method('findBy') + ->with([ + 'email' => 'new@example.com', + ]) + ->willReturn([$user]); + + $this->entityManager + ->expects($this->once()) + ->method('getRepository') + ->with(User::class) + ->willReturn($repository); + + $handler = new UserDataHandler($this->entityManager, $this->logger); + $resolvedUser = $handler->findLocalUser($profileResponse); + + $this->assertSame($user, $resolvedUser); + $this->assertSame(200, $user->getBusProAddressId()); + $this->assertSame(100, $user->getBusProPersonId()); + } + + public function testFindLocalUserSkipsEmailFallbackWhenMultipleUsersExist(): void + { + $userA = (new User()) + ->setFirstName('A') + ->setLastName('A') + ->setEmail('new@example.com') + ->setBusProAddressId(1) + ->setBusProPersonId(2) + ; + $userB = (new User()) + ->setFirstName('B') + ->setLastName('B') + ->setEmail('new@example.com') + ->setBusProAddressId(3) + ->setBusProPersonId(4) + ; + + $profileResponse = $this->createProfileResponse(); + + $repository = $this->createMock(ObjectRepository::class); + $repository + ->expects($this->once()) + ->method('findOneBy') + ->willReturn(null); + $repository + ->expects($this->once()) + ->method('findBy') + ->willReturn([$userA, $userB]); + + $this->entityManager + ->expects($this->once()) + ->method('getRepository') + ->with(User::class) + ->willReturn($repository); + + $this->logger + ->expects($this->once()) + ->method('warning') + ->with( + 'Unable to match local user by email: multiple users found', + [ + 'email' => 'new@example.com', + 'count' => 2, + ], + ); + + $handler = new UserDataHandler($this->entityManager, $this->logger); + $resolvedUser = $handler->findLocalUser($profileResponse); + + $this->assertNull($resolvedUser); + } + + public function testFindLocalUserReturnsUniqueEmailMatchWhenBusProIdsAreMissing(): void + { + $user = (new User()) + ->setFirstName('First') + ->setLastName('Last') + ->setEmail('new@example.com') + ->setBusProAddressId(1) + ->setBusProPersonId(2) + ; + + $profileResponse = $this->createProfileResponse(null, null); + + $repository = $this->createMock(ObjectRepository::class); + $repository + ->expects($this->once()) + ->method('findOneBy') + ->with([ + 'busProAddressId' => null, + 'busProPersonId' => null, + ]) + ->willReturn(null); + $repository + ->expects($this->once()) + ->method('findBy') + ->with([ + 'email' => 'new@example.com', + ]) + ->willReturn([$user]); + + $this->entityManager + ->expects($this->once()) + ->method('getRepository') + ->with(User::class) + ->willReturn($repository); + + $this->logger + ->expects($this->once()) + ->method('warning') + ->with( + 'Skip BusPro ID refresh: profile response missing IDs', + [ + 'user_id' => null, + 'user_email' => 'new@example.com', + 'bus_pro_address_id' => null, + 'bus_pro_person_id' => null, + ], + ); + + $handler = new UserDataHandler($this->entityManager, $this->logger); + $resolvedUser = $handler->findLocalUser($profileResponse); + + $this->assertSame($user, $resolvedUser); + $this->assertSame(1, $user->getBusProAddressId()); + $this->assertSame(2, $user->getBusProPersonId()); + } + + private function createProfileResponse(?int $addressId = 200, ?int $personId = 100): ProfileResponse + { + $address = (new BusProAddress()) + ->setStreet('New Street 123') + ->setPostCode('54321') + ->setCity('New City') + ->setCountry('AT') + ; + + $communication = (new BusProCommunication()) + ->setPhone('999') + ->setMobile('888') + ->setEmail('new@example.com') + ; + + return (new ProfileResponse()) + ->setAddressId($addressId) + ->setPersonId($personId) + ->setFirstName('New') + ->setName('Lastname') + ->setTitle('Prof.') + ->setSalutation('Frau') + ->setGender('F') + ->setDateOfBirth(new \DateTimeImmutable('1995-12-24')) + ->setAddress($address) + ->setCommunication($communication) + ; + } +}