From 96b7b3f451ffe911fc0798b24f69e3c14a4ec8bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 23 Sep 2026 17:21:46 +0200 Subject: [PATCH] fix: anonymize user first and last names --- src/Service/DatabaseAnonymizer.php | 17 +++++++++ tests/Service/DatabaseAnonymizerTest.php | 48 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/Service/DatabaseAnonymizer.php b/src/Service/DatabaseAnonymizer.php index b12e751..bfaca92 100644 --- a/src/Service/DatabaseAnonymizer.php +++ b/src/Service/DatabaseAnonymizer.php @@ -97,6 +97,15 @@ class DatabaseAnonymizer return $report; } + /** + * Looked up by email alone, never by name: resolveIdentity() would register a name alias, + * and two accounts of the same person — an intended state here — would then collapse onto + * one synthetic identity and so onto one synthetic email, which User::$email is unique on. + * + * The names are only replaced where one is already set. Most accounts carry none until the + * BusPro import fills them in, and minting a name for those would turn an incomplete profile + * into one that looks complete while profileComplete still says otherwise. + */ public function anonymizeUser(User $user): void { $identity = $this->resolveIdentity( @@ -104,6 +113,14 @@ class DatabaseAnonymizer ); $user->setEmail($identity['email']); + + if ($this->hasReplaceableValue($user->getFirstName())) { + $user->setFirstName($identity['firstName']); + } + + if ($this->hasReplaceableValue($user->getLastName())) { + $user->setLastName($identity['lastName']); + } } public function anonymizeNewsletterConsent(NewsletterConsent $consent): void diff --git a/tests/Service/DatabaseAnonymizerTest.php b/tests/Service/DatabaseAnonymizerTest.php index cf965fa..32cf14e 100644 --- a/tests/Service/DatabaseAnonymizerTest.php +++ b/tests/Service/DatabaseAnonymizerTest.php @@ -26,6 +26,8 @@ class DatabaseAnonymizerTest extends TestCase ); $user = new User('Mia.Muster@example.com'); + $user->setFirstName('Mia'); + $user->setLastName('Muster'); $user->setPassword('plain-text-password'); $user->setPersonId(12345); $user->setAddressId(67890); @@ -162,6 +164,10 @@ class DatabaseAnonymizerTest extends TestCase $service->anonymizeAccommodationBooking($booking); self::assertStringEndsWith('@example.test', $user->getEmail()); + self::assertNotSame('Mia', $user->getFirstName()); + self::assertNotSame('Muster', $user->getLastName()); + self::assertNotNull($user->getFirstName()); + self::assertNotNull($user->getLastName()); self::assertSame('plain-text-password', $user->getPassword()); self::assertSame(12345, $user->getPersonId()); self::assertSame(67890, $user->getAddressId()); @@ -254,4 +260,46 @@ class DatabaseAnonymizerTest extends TestCase self::assertNull($booking->getCity()); self::assertNull($booking->getRemarks()); } + + /** + * Most accounts carry no name until the BusPro import fills one in. Minting one here would + * turn an incomplete profile into one that reads as complete while profileComplete still + * says otherwise. + */ + public function testAnonymizeUserLeavesUnsetNamesNull(): void + { + $service = new DatabaseAnonymizer( + $this->createStub(EntityManagerInterface::class), + new NullLogger(), + ); + + $user = new User('Mia.Muster@example.com'); + + $service->anonymizeUser($user); + + self::assertStringEndsWith('@example.test', (string) $user->getEmail()); + self::assertNull($user->getFirstName()); + self::assertNull($user->getLastName()); + } + + /** + * Two accounts of the same person are an intended state, and User::$email is unique. The + * identity is therefore looked up by email alone: resolving it by name too would hand both + * accounts the same synthetic email and break the run on a constraint violation. + */ + public function testAnonymizeUserGivesTwoAccountsOfTheSamePersonDistinctEmails(): void + { + $service = new DatabaseAnonymizer( + $this->createStub(EntityManagerInterface::class), + new NullLogger(), + ); + + $private = (new User('mia.muster@example.com'))->setFirstName('Mia')->setLastName('Muster'); + $staff = (new User('m.muster@ep-reisen.de'))->setFirstName('Mia')->setLastName('Muster'); + + $service->anonymizeUser($private); + $service->anonymizeUser($staff); + + self::assertNotSame($private->getEmail(), $staff->getEmail()); + } }