From a36d22d72b1d38124ae8400194059b0cf5bbe0d7 Mon Sep 17 00:00:00 2001
From: Benjamin Neff <benjamin@coding4coffee.ch>
Date: Mon, 16 Oct 2017 23:00:21 +0200
Subject: [PATCH] Handle duplicate account migrations

closes #7641
---
 Changelog.md                                  |  1 +
 lib/diaspora/federation/receive.rb            | 11 ++++++----
 .../receive_federation_messages_spec.rb       |  9 ++++----
 spec/lib/diaspora/federation/receive_spec.rb  | 21 +++++++++++++++++++
 4 files changed, 34 insertions(+), 8 deletions(-)

diff --git a/Changelog.md b/Changelog.md
index 5787dc9ca6..b6ce3f0666 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -38,6 +38,7 @@ after you've upgraded.
 * Cleanup relayables where the signature is missing [#7637](https://github.com/diaspora/diaspora/pull/7637)
 * Avoid page to jump to top after a post deletion [#7638](https://github.com/diaspora/diaspora/pull/7638)
 * Handle duplicate account deletions [#7639](https://github.com/diaspora/diaspora/pull/7639)
+* Handle duplicate account migrations [#7641](https://github.com/diaspora/diaspora/pull/7641)
 
 ## Features
 * Ask for confirmation when leaving a submittable comment field [#7530](https://github.com/diaspora/diaspora/pull/7530)
diff --git a/lib/diaspora/federation/receive.rb b/lib/diaspora/federation/receive.rb
index 13e235de11..d4dcea34dd 100644
--- a/lib/diaspora/federation/receive.rb
+++ b/lib/diaspora/federation/receive.rb
@@ -18,11 +18,14 @@ module Diaspora
       end
 
       def self.account_migration(entity)
+        old_person = author_of(entity)
         profile = profile(entity.profile)
-        AccountMigration.create!(
-          old_person: Person.by_account_identifier(entity.author),
-          new_person: profile.person
-        )
+        return if AccountMigration.where(old_person: old_person, new_person: profile.person).exists?
+        AccountMigration.create!(old_person: old_person, new_person: profile.person)
+      rescue => e # rubocop:disable Lint/RescueWithoutErrorClass
+        raise e unless AccountMigration.where(old_person: old_person, new_person: profile.person).exists?
+        logger.warn "ignoring error on receive #{entity}: #{e.class}: #{e.message}"
+        nil
       end
 
       def self.comment(entity)
diff --git a/spec/integration/federation/receive_federation_messages_spec.rb b/spec/integration/federation/receive_federation_messages_spec.rb
index 5ab47dbcc2..c9a67aeb49 100644
--- a/spec/integration/federation/receive_federation_messages_spec.rb
+++ b/spec/integration/federation/receive_federation_messages_spec.rb
@@ -58,11 +58,12 @@ describe "Receive federation messages feature" do
           expect(AccountMigration.find_by(old_person: sender.person, new_person: new_user.person)).to be_performed
         end
 
-        it "doesn't accept the same migration for the second time" do
+        it "doesn't run the same migration for the second time" do
           run_migration
-          expect {
-            run_migration
-          }.to raise_error(ActiveRecord::RecordInvalid)
+          expect_any_instance_of(AccountMigration).not_to receive(:perform!)
+          run_migration
+          expect(AccountMigration.where(old_person: sender.person, new_person: new_user.person).count).to eq(1)
+          expect(AccountMigration.find_by(old_person: sender.person, new_person: new_user.person)).to be_performed
         end
 
         it "doesn't accept second migration for the same sender" do
diff --git a/spec/lib/diaspora/federation/receive_spec.rb b/spec/lib/diaspora/federation/receive_spec.rb
index 1fd76b08d1..2c2e7e1106 100644
--- a/spec/lib/diaspora/federation/receive_spec.rb
+++ b/spec/lib/diaspora/federation/receive_spec.rb
@@ -47,6 +47,27 @@ describe Diaspora::Federation::Receive do
 
       expect(AccountMigration.exists?(old_person: sender, new_person: new_person)).to be_truthy
     end
+
+    it "ignores duplicate the account migrations" do
+      AccountMigration.create(old_person: sender, new_person: new_person)
+
+      expect(AccountMigration).not_to receive(:create!)
+
+      expect(Diaspora::Federation::Receive.account_migration(account_migration_entity)).to be_nil
+
+      expect(AccountMigration.exists?(old_person: sender, new_person: new_person)).to be_truthy
+    end
+
+    it "handles race conditions on parallel receive" do
+      expect(AccountMigration).to receive(:create!) do
+        AccountMigration.create(old_person: sender, new_person: new_person)
+        raise "Some database error"
+      end
+
+      expect(Diaspora::Federation::Receive.account_migration(account_migration_entity)).to be_nil
+
+      expect(AccountMigration.exists?(old_person: sender, new_person: new_person)).to be_truthy
+    end
   end
 
   describe ".comment" do
-- 
GitLab