From 84f4ee87c5a0c73f3070ab8de3cb04a9a26e3e2a Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Tue, 19 May 2026 17:03:21 +0100 Subject: [PATCH] fix(support): resolve duplicate email for profile updates When multiple users share an email, update the account that still needs the requested first/last name instead of failing ambiguous match. Co-authored-by: Cursor --- .../Support/UserProfileUpdateService.php | 56 ++++++++++++++++--- .../Support/UserProfileUpdateServiceTest.php | 36 ++++++++++++ 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/app/Services/Support/UserProfileUpdateService.php b/app/Services/Support/UserProfileUpdateService.php index 0166434a1..ef676079e 100644 --- a/app/Services/Support/UserProfileUpdateService.php +++ b/app/Services/Support/UserProfileUpdateService.php @@ -4,6 +4,7 @@ use App\Models\Support\SupportCase; use App\User; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; @@ -66,12 +67,17 @@ public function updateProfile( return SupportJson::fail($tool, $input, 'no_matching_user_found'); } - if ($matches->count() !== 1) { - return SupportJson::fail($tool, $input, 'ambiguous_user_match'); + $user = $this->resolveUserForProfileUpdate($matches, $firstname, $lastname); + if ($user === null) { + return SupportJson::fail($tool, $input, [ + 'ambiguous_user_match', + 'matched_user_ids: '.$matches->pluck('id')->implode(','), + ]); } - /** @var User $user */ - $user = $matches->first(); + $warnings = $matches->count() > 1 + ? ['resolved_duplicate_email_to_user_id:'.$user->id] + : []; $before = [ 'user_id' => $user->id, @@ -95,7 +101,7 @@ public function updateProfile( 'before' => $before, 'after' => $before, 'note' => 'profile_already_matches_requested_values', - ]); + ], $warnings); } $planned = [ @@ -114,7 +120,7 @@ public function updateProfile( 'changes_applied' => [], 'before' => $before, 'after' => $after, - ]); + ], $warnings); } if (config('support_gmail.dry_run') && !$viaEmailApproval) { @@ -145,7 +151,43 @@ public function updateProfile( 'changes_applied' => [$planned], 'before' => $before, 'after' => $after, - ]); + ], $warnings); + } + + /** + * When multiple users share an email, pick the one that still needs the requested name change. + * + * @param Collection $matches + */ + private function resolveUserForProfileUpdate(Collection $matches, ?string $firstname, ?string $lastname): ?User + { + if ($matches->count() === 1) { + return $matches->first(); + } + + $active = $matches->filter(fn (User $user) => $user->deleted_at === null)->values(); + if ($active->count() === 1) { + return $active->first(); + } + + $candidates = $active->isNotEmpty() ? $active : $matches->values(); + + $needingUpdate = $candidates->filter(function (User $user) use ($firstname, $lastname) { + if ($firstname !== null && $firstname !== $user->firstname) { + return true; + } + if ($lastname !== null && $lastname !== $user->lastname) { + return true; + } + + return false; + }); + + if ($needingUpdate->count() === 1) { + return $needingUpdate->first(); + } + + return null; } private function isValidEmail(string $email): bool diff --git a/tests/Unit/Support/UserProfileUpdateServiceTest.php b/tests/Unit/Support/UserProfileUpdateServiceTest.php index 35cd6fef9..284225c95 100644 --- a/tests/Unit/Support/UserProfileUpdateServiceTest.php +++ b/tests/Unit/Support/UserProfileUpdateServiceTest.php @@ -43,6 +43,42 @@ public function test_profile_update_dry_run_plans_change(): void $this->assertSame('Bernard Hanna', $u->firstname); } + public function test_profile_update_resolves_duplicate_email_to_user_needing_change(): void + { + User::factory()->create([ + 'email' => 'dup@example.com', + 'firstname' => 'Bernard', + 'lastname' => 'Hanna', + ]); + User::factory()->create([ + 'email' => 'dup@example.com', + 'firstname' => 'Bernard Hanna', + 'lastname' => '', + ]); + + $case = SupportCase::create([ + 'source_channel' => 'manual', + 'processing_mode' => 'manual', + 'subject' => 'test', + 'raw_message' => 'test', + 'status' => 'investigating', + 'risk_level' => 'low', + 'correlation_id' => 'cid', + ]); + + $payload = app(UserProfileUpdateService::class)->updateProfile( + $case, + 'dup@example.com', + 'Bernard', + 'Hanna', + true, + ); + + $this->assertTrue($payload['ok']); + $this->assertSame('Bernard Hanna', $payload['result']['before']['firstname']); + $this->assertStringContainsString('resolved_duplicate_email_to_user_id:', (string) ($payload['warnings'][0] ?? '')); + } + public function test_profile_update_execute_changes_user(): void { config(['support_gmail.dry_run' => true]);