2017-04-06 17:03:07 +00:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2017-07-10 14:24:02 +00:00
|
|
|
describe Users::MigrateToGhostUserService do
|
2017-04-06 17:03:07 +00:00
|
|
|
let!(:user) { create(:user) }
|
2017-08-01 18:51:52 +00:00
|
|
|
let!(:project) { create(:project, :repository) }
|
2017-04-06 17:03:07 +00:00
|
|
|
let(:service) { described_class.new(user) }
|
|
|
|
|
|
|
|
context "migrating a user's associated records to the ghost user" do
|
|
|
|
context 'issues' do
|
2017-07-17 14:38:43 +00:00
|
|
|
context 'deleted user is present as both author and edited_user' do
|
|
|
|
include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:author, :last_edited_by] do
|
|
|
|
let(:created_record) do
|
|
|
|
create(:issue, project: project, author: user, last_edited_by: user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'deleted user is present only as edited_user' do
|
|
|
|
include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:last_edited_by] do
|
|
|
|
let(:created_record) { create(:issue, project: project, author: create(:user), last_edited_by: user) }
|
|
|
|
end
|
2017-04-06 17:03:07 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'merge requests' do
|
2017-07-17 14:38:43 +00:00
|
|
|
context 'deleted user is present as both author and merge_user' do
|
|
|
|
include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest, [:author, :merge_user] do
|
|
|
|
let(:created_record) { create(:merge_request, source_project: project, author: user, merge_user: user, target_branch: "first") }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'deleted user is present only as both merge_user' do
|
|
|
|
include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest, [:merge_user] do
|
|
|
|
let(:created_record) { create(:merge_request, source_project: project, merge_user: user, target_branch: "first") }
|
|
|
|
end
|
2017-04-06 17:03:07 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'notes' do
|
|
|
|
include_examples "migrating a deleted user's associated records to the ghost user", Note do
|
|
|
|
let(:created_record) { create(:note, project: project, author: user) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'abuse reports' do
|
|
|
|
include_examples "migrating a deleted user's associated records to the ghost user", AbuseReport do
|
|
|
|
let(:created_record) { create(:abuse_report, reporter: user, user: create(:user)) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'award emoji' do
|
2017-07-17 14:38:43 +00:00
|
|
|
include_examples "migrating a deleted user's associated records to the ghost user", AwardEmoji, [:user] do
|
2017-04-06 17:03:07 +00:00
|
|
|
let(:created_record) { create(:award_emoji, user: user) }
|
|
|
|
|
|
|
|
context "when the awardable already has an award emoji of the same name assigned to the ghost user" do
|
|
|
|
let(:awardable) { create(:issue) }
|
|
|
|
let!(:existing_award_emoji) { create(:award_emoji, user: User.ghost, name: "thumbsup", awardable: awardable) }
|
|
|
|
let!(:award_emoji) { create(:award_emoji, user: user, name: "thumbsup", awardable: awardable) }
|
|
|
|
|
|
|
|
it "migrates the award emoji regardless" do
|
|
|
|
service.execute
|
|
|
|
|
|
|
|
migrated_record = AwardEmoji.find_by_id(award_emoji.id)
|
|
|
|
|
|
|
|
expect(migrated_record.user).to eq(User.ghost)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not leave the migrated award emoji in an invalid state" do
|
|
|
|
service.execute
|
|
|
|
|
|
|
|
migrated_record = AwardEmoji.find_by_id(award_emoji.id)
|
|
|
|
|
|
|
|
expect(migrated_record).to be_valid
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
Move records to the ghost user in a transaction.
- While deleting a user, some of the user's associated records are moved to the
ghost user so they aren't deleted. The user is blocked before these records
are moved, to prevent the user from creating new records while the migration
is happening, and so preventing a data race.
- Previously, if the migration failed, the user would _remain_ blocked, which is
not the expected behavior. On the other hand, we can't just stick the block +
migration into a transaction, because we want the block to be committed before
the migration starts (for the data race reason mentioned above).
- One solution (implemented in this commit) is to block the user in a parent
transaction, migrate the associated records in a nested sub-transaction, and
then unblock the user in the parent transaction if the sub-transaction fails.
2017-04-21 05:55:24 +00:00
|
|
|
|
|
|
|
context "when record migration fails with a rollback exception" do
|
|
|
|
before do
|
|
|
|
expect_any_instance_of(MergeRequest::ActiveRecord_Associations_CollectionProxy)
|
|
|
|
.to receive(:update_all).and_raise(ActiveRecord::Rollback)
|
|
|
|
end
|
|
|
|
|
|
|
|
context "for records that were already migrated" do
|
|
|
|
let!(:issue) { create(:issue, project: project, author: user) }
|
|
|
|
let!(:merge_request) { create(:merge_request, source_project: project, author: user, target_branch: "first") }
|
|
|
|
|
|
|
|
it "reverses the migration" do
|
|
|
|
service.execute
|
|
|
|
|
|
|
|
expect(issue.reload.author).to eq(user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-04-06 17:03:07 +00:00
|
|
|
end
|
|
|
|
end
|