From 1fea2bf1c58388ae18d633f7d92717d6efd49069 Mon Sep 17 00:00:00 2001 From: Natalie Tay Date: Tue, 16 Apr 2024 17:37:33 +0800 Subject: [PATCH] FEATURE: Merge user associated accounts, favouring the target user upon conflict (#26645) --- app/services/user_merger.rb | 24 ++++++++++++ config/locales/server.en.yml | 1 + .../user_associated_account_fabricator.rb | 8 ++++ spec/services/user_merger_spec.rb | 38 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 spec/fabricators/user_associated_account_fabricator.rb diff --git a/app/services/user_merger.rb b/app/services/user_merger.rb index eef3c81cb51..3f93d497af0 100644 --- a/app/services/user_merger.rb +++ b/app/services/user_merger.rb @@ -18,6 +18,7 @@ class UserMerger merge_user_visits update_site_settings merge_user_attributes + merge_user_associated_accounts DiscourseEvent.trigger(:merging_users, @source_user, @target_user) update_user_stats @@ -294,6 +295,29 @@ class UserMerger SQL end + def merge_user_associated_accounts + if @acting_user + ::MessageBus.publish "/merge_user", + { + message: + I18n.t("admin.user.merge_user.merging_user_associated_accounts"), + }, + user_ids: [@acting_user.id] + end + + UserAssociatedAccount.where(user_id: @source_user.id).update_all(<<~SQL) + user_id = CASE + WHEN EXISTS ( + SELECT 1 + FROM user_associated_accounts AS conflicts + WHERE (conflicts.user_id = #{@target_user.id} AND conflicts.provider_name = user_associated_accounts.provider_name) + ) + THEN NULL + ELSE #{@target_user.id} + END + SQL + end + def update_user_ids if @acting_user ::MessageBus.publish "/merge_user", diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index c1f0ca34690..41a4cf2eee0 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2931,6 +2931,7 @@ en: updating_site_settings: "Updating site settings…" updating_user_stats: "Updating user stats…" merging_user_attributes: "Merging user attributes…" + merging_user_associated_accounts: "Merging user associated accounts…" updating_user_ids: "Updating user ids…" deleting_source_user: "Deleting source user…" user: diff --git a/spec/fabricators/user_associated_account_fabricator.rb b/spec/fabricators/user_associated_account_fabricator.rb new file mode 100644 index 00000000000..edbfe881cca --- /dev/null +++ b/spec/fabricators/user_associated_account_fabricator.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +Fabricator(:user_associated_account) do + provider_name "meecrosof" + provider_uid { sequence(:key) { |i| "#{i + 1}" } } + user + info { |attrs| { name: attrs[:user].username, email: attrs[:user].email } } +end diff --git a/spec/services/user_merger_spec.rb b/spec/services/user_merger_spec.rb index 1884904b65c..6d87ae60270 100644 --- a/spec/services/user_merger_spec.rb +++ b/spec/services/user_merger_spec.rb @@ -1084,6 +1084,44 @@ RSpec.describe UserMerger do end end + context "with user associated accounts (UAAs)" do + context "when only merging account has UAAs" do + it "transfers the source user UAA to the target" do + source_uaa = Fabricate(:user_associated_account, user: source_user) + + merge_users! + + expect(source_uaa.reload.user).to eq(target_user) + end + end + + context "when both accounts have UAAs" do + context "when both accounts' UAAs have different provider_names" do + it "transfers the source user UAA to the target and keeps both" do + source_uaa = Fabricate(:user_associated_account, user: source_user, provider_name: "x") + target_uaa = Fabricate(:user_associated_account, user: target_user, provider_name: "y") + + merge_users! + + expect(target_uaa.reload.user).to eq(target_user) + expect(source_uaa.reload.user).to eq(target_user) + end + end + + context "when both accounts' UAAs have same provider_names" do + it "keeps only the target UAA" do + source_uaa = Fabricate(:user_associated_account, user: source_user, provider_name: "x") + target_uaa = Fabricate(:user_associated_account, user: target_user, provider_name: "x") + + merge_users! + + expect(target_uaa.reload.user).to eq(target_user) + expect(source_uaa.reload.user).to eq(nil) + end + end + end + end + it "updates users" do walter.update!(approved_by: source_user) upload = Fabricate(:upload)