discourse/spec/services/user_merger_spec.rb
Gerhard Schlager b970b072f6 FIX: User merge should not fail when primary email address is missing
The merge process might move all email addresses of the source user to the target user. Destroying the source user failed in that case.
2018-06-01 16:23:21 +02:00

1032 lines
38 KiB
Ruby

require 'rails_helper'
describe UserMerger do
let!(:target_user) { Fabricate(:user_single_email, username: 'alice', email: 'alice@example.com') }
let!(:source_user) { Fabricate(:user_single_email, username: 'alice1', email: 'alice@work.com') }
let(:walter) { Fabricate(:walter_white) }
def merge_users!(source = nil, target = nil)
source ||= source_user
target ||= target_user
UserMerger.new(source, target).merge!
end
it "changes owner of topics and posts" do
topic1 = Fabricate(:topic, user: source_user)
post1 = Fabricate(:post, topic: topic1, user: source_user)
post2 = Fabricate(:post, topic: topic1, user: walter)
post3 = Fabricate(:post, topic: topic1, user: target_user)
post4 = Fabricate(:post, topic: topic1, user: walter)
post5 = Fabricate(:post, topic: topic1, user: source_user)
topic2 = Fabricate(:topic, user: walter)
post6 = Fabricate(:post, topic: topic2, user: walter)
post7 = Fabricate(:post, topic: topic2, user: source_user)
post8 = Fabricate(:post, topic: topic2, user: source_user, deleted_at: Time.now)
merge_users!
[topic1, post1, post3, post5, post7, post8].each do |x|
expect(x.reload.user).to eq(target_user)
end
[post2, post4, topic2, post6].each do |x|
expect(x.reload.user).to eq(walter)
end
end
it "changes owner of personal messages" do
pm_topic = Fabricate(:private_message_topic)
post1 = Fabricate(:post, topic: pm_topic, user: source_user)
post2 = Fabricate(:post, topic: pm_topic, user: walter)
post3 = Fabricate(:post, topic: pm_topic, user: target_user)
post4 = Fabricate(:post, topic: pm_topic, user: source_user, deleted_at: Time.now)
merge_users!
expect(post1.reload.user).to eq(target_user)
expect(post2.reload.user).to eq(walter)
expect(post3.reload.user).to eq(target_user)
expect(post4.reload.user).to eq(target_user)
end
it "changes owner of categories" do
category = Fabricate(:category, user: source_user)
merge_users!
expect(category.reload.user).to eq(target_user)
end
it "merges category notification settings" do
category1 = Fabricate(:category)
category2 = Fabricate(:category)
category3 = Fabricate(:category)
watching = CategoryUser.notification_levels[:watching]
CategoryUser.batch_set(source_user, :watching, [category1.id, category2.id])
CategoryUser.batch_set(target_user, :watching, [category2.id, category3.id])
merge_users!
category_ids = CategoryUser.where(user_id: target_user.id, notification_level: watching).pluck(:category_id)
expect(category_ids).to contain_exactly(category1.id, category2.id, category3.id)
category_ids = CategoryUser.where(user_id: source_user.id, notification_level: watching).pluck(:category_id)
expect(category_ids).to be_empty
end
context "developer flag" do
it "moves the developer flag when the target user isn't a developer yet" do
Developer.create!(user_id: source_user.id)
merge_users!
expect(Developer.where(user_id: source_user.id).count).to eq(0)
expect(Developer.where(user_id: target_user.id).count).to eq(1)
end
it "deletes the source's developer flag when the target user is already a developer" do
Developer.create!(user_id: source_user.id)
Developer.create!(user_id: target_user.id)
merge_users!
expect(Developer.where(user_id: source_user.id).count).to eq(0)
expect(Developer.where(user_id: target_user.id).count).to eq(1)
end
end
context "drafts" do
def create_draft(user, key, text)
seq = DraftSequence.next!(user, key)
Draft.set(user, key, seq, text)
end
def current_target_user_draft(key)
seq = DraftSequence.current(target_user, key)
Draft.get(target_user, key, seq)
end
it "merges drafts" do
key_topic_17 = "#{Draft::EXISTING_TOPIC}#{17}"
key_topic_19 = "#{Draft::EXISTING_TOPIC}#{19}"
create_draft(source_user, Draft::NEW_TOPIC, 'new topic draft by alice1')
create_draft(source_user, key_topic_17, 'draft by alice1')
create_draft(source_user, key_topic_19, 'draft by alice1')
create_draft(target_user, key_topic_19, 'draft by alice')
merge_users!
expect(current_target_user_draft(Draft::NEW_TOPIC)).to eq('new topic draft by alice1')
expect(current_target_user_draft(key_topic_17)).to eq('draft by alice1')
expect(current_target_user_draft(key_topic_19)).to eq('draft by alice')
expect(DraftSequence.where(user_id: source_user.id).count).to eq(0)
expect(Draft.where(user_id: source_user.id).count).to eq(0)
end
end
it "updates email logs" do
Fabricate(:email_log, user: source_user)
merge_users!
expect(EmailLog.where(user_id: source_user.id).count).to eq(0)
expect(EmailLog.where(user_id: target_user.id).count).to eq(1)
end
context "likes" do
def given_daily_like_count_for(user, date)
GivenDailyLike.find_for(user.id, date).pluck(:likes_given)[0] || 0
end
it "merges likes" do
p1 = Fabricate(:post)
p2 = Fabricate(:post)
p3 = Fabricate(:post)
p4 = Fabricate(:post)
p5 = Fabricate(:post)
p6 = Fabricate(:post)
now = Time.zone.now
freeze_time(now - 1.day)
PostAction.act(source_user, p1, PostActionType.types[:like])
PostAction.act(source_user, p2, PostActionType.types[:like])
PostAction.act(target_user, p2, PostActionType.types[:like])
PostAction.act(target_user, p3, PostActionType.types[:like])
freeze_time(now)
PostAction.act(source_user, p4, PostActionType.types[:like])
PostAction.act(source_user, p5, PostActionType.types[:like])
PostAction.act(target_user, p5, PostActionType.types[:like])
PostAction.act(source_user, p6, PostActionType.types[:like])
PostAction.remove_act(source_user, p6, PostActionType.types[:like])
merge_users!
[p1, p2, p3, p4, p5].each { |p| expect(p.reload.like_count).to eq(1) }
expect(PostAction.with_deleted.where(user_id: source_user.id).count).to eq(0)
expect(PostAction.with_deleted.where(user_id: target_user.id).count).to eq(6)
expect(given_daily_like_count_for(source_user, Date.yesterday)).to eq(0)
expect(given_daily_like_count_for(target_user, Date.yesterday)).to eq(3)
expect(given_daily_like_count_for(source_user, Date.today)).to eq(0)
expect(given_daily_like_count_for(target_user, Date.today)).to eq(2)
end
end
it "updates group history" do
group = Fabricate(:group)
group.add_owner(source_user)
logger = GroupActionLogger.new(source_user, group)
logger.log_add_user_to_group(walter)
logger.log_add_user_to_group(target_user)
group = Fabricate(:group)
group.add_owner(target_user)
logger = GroupActionLogger.new(target_user, group)
logger.log_add_user_to_group(walter)
logger.log_add_user_to_group(source_user)
merge_users!
expect(GroupHistory.where(acting_user_id: source_user.id).count).to eq(0)
expect(GroupHistory.where(acting_user_id: target_user.id).count).to eq(4)
expect(GroupHistory.where(target_user_id: source_user.id).count).to eq(0)
expect(GroupHistory.where(target_user_id: target_user.id).count).to eq(2)
end
it "merges group memberships" do
group1 = Fabricate(:group)
group1.add_owner(target_user)
group1.bulk_add([walter.id, source_user.id])
group2 = Fabricate(:group)
group2.bulk_add([walter.id, target_user.id])
group3 = Fabricate(:group)
group3.add_owner(source_user)
group3.add(walter)
merge_users!
[group1, group2, group3].each do |g|
owner = [group1, group3].include?(g)
expect(GroupUser.where(group_id: g.id, user_id: target_user.id, owner: owner).count).to eq(1)
expect(Group.where(id: g.id).pluck(:user_count).first).to eq(2)
end
expect(GroupUser.where(user_id: source_user.id).count).to eq(0)
end
it "updates incoming emails" do
email = Fabricate(:incoming_email, user: source_user)
merge_users!
expect(email.reload.user).to eq(target_user)
end
it "updates incoming links" do
link1 = Fabricate(:incoming_link, user: source_user)
link2 = Fabricate(:incoming_link, current_user_id: source_user.id)
merge_users!
expect(link1.reload.user).to eq(target_user)
expect(link2.reload.current_user_id).to eq(target_user.id)
end
it "updates invites" do
invite1 = Fabricate(:invite, invited_by: walter, user: source_user)
invite2 = Fabricate(:invite, invited_by: source_user)
invite3 = Fabricate(:invite, invited_by: source_user)
invite3.trash!(source_user)
merge_users!
[invite1, invite2, invite3].each { |x| x.reload }
expect(invite1.user).to eq(target_user)
expect(invite2.invited_by).to eq(target_user)
expect(invite3.invited_by).to eq(target_user)
expect(invite3.deleted_by).to eq(target_user)
end
it "merges muted users" do
muted1 = Fabricate(:user)
muted2 = Fabricate(:user)
muted3 = Fabricate(:user)
coding_horror = Fabricate(:coding_horror)
MutedUser.create!(user_id: source_user.id, muted_user_id: muted1.id)
MutedUser.create!(user_id: source_user.id, muted_user_id: muted2.id)
MutedUser.create!(user_id: target_user.id, muted_user_id: muted2.id)
MutedUser.create!(user_id: target_user.id, muted_user_id: muted3.id)
MutedUser.create!(user_id: walter.id, muted_user_id: source_user.id)
MutedUser.create!(user_id: coding_horror.id, muted_user_id: source_user.id)
MutedUser.create!(user_id: coding_horror.id, muted_user_id: target_user.id)
merge_users!
[muted1, muted2, muted3].each do |m|
expect(MutedUser.where(user_id: target_user.id, muted_user_id: m.id).count).to eq(1)
end
expect(MutedUser.where(user_id: source_user.id).count).to eq(0)
expect(MutedUser.where(user_id: walter.id, muted_user_id: target_user.id).count).to eq(1)
expect(MutedUser.where(user_id: coding_horror.id, muted_user_id: target_user.id).count).to eq(1)
expect(MutedUser.where(muted_user_id: source_user.id).count).to eq(0)
end
context "notifications" do
it "updates notifications" do
Fabricate(:notification, user: source_user)
Fabricate(:notification, user: source_user)
Fabricate(:notification, user: walter)
merge_users!
expect(Notification.where(user_id: target_user.id).count).to eq(2)
expect(Notification.where(user_id: source_user.id).count).to eq(0)
end
end
context "post actions" do
it "merges post actions" do
p1 = Fabricate(:post)
p2 = Fabricate(:post)
p3 = Fabricate(:post)
type_ids = PostActionType.public_type_ids + [PostActionType.flag_types.values.first]
type_ids.each do |type|
PostAction.act(source_user, p1, type)
PostAction.act(source_user, p2, type)
PostAction.act(target_user, p2, type)
PostAction.act(target_user, p3, type)
end
merge_users!
type_ids.each do |type|
expect(PostAction.where(user_id: target_user.id, post_action_type_id: type)
.pluck(:post_id)).to contain_exactly(p1.id, p2.id, p3.id)
end
expect(PostAction.where(user_id: source_user.id).count).to eq(0)
end
it "updates post actions" do
p1 = Fabricate(:post)
p2 = Fabricate(:post)
p3 = Fabricate(:post)
p4 = Fabricate(:post)
action1 = PostAction.act(source_user, p1, PostActionType.flag_types[:off_topic])
action1.update_attribute(:deleted_by_id, source_user.id)
action2 = PostAction.act(source_user, p2, PostActionType.flag_types[:off_topic])
action2.update_attribute(:deferred_by_id, source_user.id)
action3 = PostAction.act(source_user, p3, PostActionType.flag_types[:off_topic])
action3.update_attribute(:agreed_by_id, source_user.id)
action4 = PostAction.act(source_user, p4, PostActionType.flag_types[:off_topic])
action4.update_attribute(:disagreed_by_id, source_user.id)
merge_users!
expect(action1.reload.deleted_by_id).to eq(target_user.id)
expect(action2.reload.deferred_by_id).to eq(target_user.id)
expect(action3.reload.agreed_by_id).to eq(target_user.id)
expect(action4.reload.disagreed_by_id).to eq(target_user.id)
end
end
it "updates post revisions" do
post = Fabricate(:post)
post_revision = Fabricate(:post_revision, post: post, user: source_user)
merge_users!
expect(post_revision.reload.user).to eq(target_user)
end
context "post timings" do
def create_post_timing(post, user, msecs)
PostTiming.create!(
topic_id: post.topic_id,
post_number: post.post_number,
user_id: user.id,
msecs: msecs
)
end
def post_timing_msecs_for(post, user)
PostTiming.where(
topic_id: post.topic_id,
post_number: post.post_number,
user_id: user.id
).pluck(:msecs)[0] || 0
end
it "merges post timings" do
post1 = Fabricate(:post)
post2 = Fabricate(:post)
post3 = Fabricate(:post)
create_post_timing(post1, source_user, 12345)
create_post_timing(post2, source_user, 9876)
create_post_timing(post2, target_user, 3333)
create_post_timing(post3, target_user, 10000)
merge_users!
expect(post_timing_msecs_for(post1, target_user)).to eq(12345)
expect(post_timing_msecs_for(post2, target_user)).to eq(13209)
expect(post_timing_msecs_for(post3, target_user)).to eq(10000)
expect(PostTiming.where(user_id: source_user.id).count).to eq(0)
end
end
context "posts" do
it "updates user ids of posts" do
source_user.update_attribute(:moderator, true)
topic = Fabricate(:topic)
Fabricate(:post, topic: topic, user: source_user)
post2 = Fabricate(:basic_reply, topic: topic, user: walter)
post2.revise(source_user, raw: "#{post2.raw} foo")
PostLocker.new(post2, source_user).lock
post2.trash!(source_user)
merge_users!
post2.reload
expect(post2.deleted_by).to eq(target_user)
expect(post2.last_editor).to eq(target_user)
expect(post2.locked_by_id).to eq(target_user.id)
expect(post2.reply_to_user).to eq(target_user)
end
it "updates post action counts" do
posts = {}
PostActionType.types.each do |type_name, type_id|
posts[type_name] = post = Fabricate(:post, user: walter)
PostAction.act(source_user, post, type_id)
PostAction.act(target_user, post, type_id)
end
merge_users!
posts.each do |type, post|
post.reload
expect(post.send("#{type}_count")).to eq(1)
end
end
end
it "updates queued posts" do
topic = Fabricate(:topic)
post1 = Fabricate(:queued_post, topic: topic, user: source_user)
post2 = Fabricate(:queued_post, topic: topic, approved_by: source_user)
post3 = Fabricate(:queued_post, topic: topic, rejected_by: source_user)
merge_users!
expect(post1.reload.user).to eq(target_user)
expect(post2.reload.approved_by).to eq(target_user)
expect(post3.reload.rejected_by).to eq(target_user)
end
describe 'search logs' do
after do
SearchLog.clear_debounce_cache!
end
it "updates search log entries" do
SearchLog.log(term: 'hello', search_type: :full_page, ip_address: '192.168.0.1', user_id: source_user.id)
SearchLog.log(term: 'world', search_type: :full_page, ip_address: '192.168.0.1', user_id: source_user.id)
SearchLog.log(term: 'star trek', search_type: :full_page, ip_address: '192.168.0.2', user_id: target_user.id)
SearchLog.log(term: 'bad', search_type: :full_page, ip_address: '192.168.0.3', user_id: walter.id)
merge_users!
expect(SearchLog.where(user_id: target_user.id).count).to eq(3)
expect(SearchLog.where(user_id: source_user.id).count).to eq(0)
expect(SearchLog.where(user_id: walter.id).count).to eq(1)
end
end
it "merges tag notification settings" do
tag1 = Fabricate(:tag)
tag2 = Fabricate(:tag)
tag3 = Fabricate(:tag)
watching = TagUser.notification_levels[:watching]
TagUser.batch_set(source_user, :watching, [tag1.name, tag2.name])
TagUser.batch_set(target_user, :watching, [tag2.name, tag3.name])
merge_users!
tag_ids = TagUser.where(user_id: target_user.id, notification_level: watching).pluck(:tag_id)
expect(tag_ids).to contain_exactly(tag1.id, tag2.id, tag3.id)
tag_ids = TagUser.where(user_id: source_user.id, notification_level: watching).pluck(:tag_id)
expect(tag_ids).to be_empty
end
it "updates themes" do
theme = Theme.create!(name: 'my name', user_id: source_user.id)
merge_users!
expect(theme.reload.user_id).to eq(target_user.id)
end
it "merges allowed users for topics" do
pm_topic1 = Fabricate(:private_message_topic, topic_allowed_users: [
Fabricate.build(:topic_allowed_user, user: target_user),
Fabricate.build(:topic_allowed_user, user: walter),
Fabricate.build(:topic_allowed_user, user: source_user)
])
pm_topic2 = Fabricate(:private_message_topic, topic_allowed_users: [
Fabricate.build(:topic_allowed_user, user: walter),
Fabricate.build(:topic_allowed_user, user: source_user)
])
merge_users!
expect(pm_topic1.allowed_users).to contain_exactly(target_user, walter)
expect(pm_topic2.allowed_users).to contain_exactly(target_user, walter)
expect(TopicAllowedUser.where(user_id: source_user.id).count).to eq(0)
end
it "updates topic embeds" do
topic_embed = Fabricate(:topic_embed, embed_url: "http://example.com/post/248")
topic_embed.trash!(source_user)
merge_users!
expect(topic_embed.reload.deleted_by).to eq(target_user)
end
it "updates topic links" do
topic = Fabricate(:topic, user: source_user)
post = Fabricate(:post_with_external_links, user: source_user, topic: topic)
TopicLink.extract_from(post)
link = topic.topic_links.first
TopicLinkClick.create!(topic_link_id: link.id, user_id: source_user.id, ip_address: '127.0.0.1')
TopicLinkClick.create!(topic_link_id: link.id, user_id: target_user.id, ip_address: '127.0.0.1')
TopicLinkClick.create!(topic_link_id: link.id, user_id: walter.id, ip_address: '127.0.0.1')
merge_users!
expect(TopicLink.where(user_id: target_user.id).count).to be > 0
expect(TopicLink.where(user_id: source_user.id).count).to eq(0)
expect(TopicLinkClick.where(user_id: target_user.id).count).to eq(2)
expect(TopicLinkClick.where(user_id: source_user.id).count).to eq(0)
expect(TopicLinkClick.where(user_id: walter.id).count).to eq(1)
end
context "topic timers" do
def create_topic_timer(topic, user, status_type, deleted_by = nil)
timer = Fabricate(:topic_timer, topic: topic, user: user, status_type: TopicTimer.types[status_type])
timer.trash!(deleted_by) if deleted_by
timer.reload
end
it "merges topic timers" do
topic1 = Fabricate(:topic)
timer1 = create_topic_timer(topic1, source_user, :close, Discourse.system_user)
timer2 = create_topic_timer(topic1, source_user, :close)
timer3 = create_topic_timer(topic1, source_user, :reminder, source_user)
timer4 = create_topic_timer(topic1, target_user, :reminder, target_user)
timer5 = create_topic_timer(topic1, source_user, :reminder)
topic2 = Fabricate(:topic)
timer6 = create_topic_timer(topic2, target_user, :close)
timer7 = create_topic_timer(topic2, target_user, :reminder, Discourse.system_user)
create_topic_timer(topic2, source_user, :reminder, Discourse.system_user)
merge_users!
[timer1, timer2, timer3, timer4, timer5, timer6, timer7].each do |t|
expect(t.reload.user).to eq(target_user)
end
expect(TopicTimer.with_deleted.where(user_id: source_user.id).count).to eq(0)
expect(TopicTimer.with_deleted.where(deleted_by_id: target_user.id).count).to eq(2)
expect(TopicTimer.with_deleted.where(deleted_by_id: source_user.id).count).to eq(0)
end
end
it "merges topic notification settings" do
topic1 = Fabricate(:topic)
topic2 = Fabricate(:topic)
topic3 = Fabricate(:topic)
watching = TopicUser.notification_levels[:watching]
Fabricate(:topic_user, notification_level: watching, topic: topic1, user: source_user)
Fabricate(:topic_user, notification_level: watching, topic: topic2, user: source_user)
Fabricate(:topic_user, notification_level: watching, topic: topic2, user: target_user)
Fabricate(:topic_user, notification_level: watching, topic: topic3, user: target_user)
merge_users!
topic_ids = TopicUser.where(user_id: target_user.id, notification_level: watching).pluck(:topic_id)
expect(topic_ids).to contain_exactly(topic1.id, topic2.id, topic3.id)
topic_ids = TopicUser.where(user_id: source_user.id, notification_level: watching).pluck(:topic_id)
expect(topic_ids).to be_empty
end
it "merges topic views" do
topic1 = Fabricate(:topic)
topic2 = Fabricate(:topic)
topic3 = Fabricate(:topic)
ip = '127.0.0.1'
TopicViewItem.add(topic1.id, ip, source_user.id)
TopicViewItem.add(topic2.id, ip, source_user.id)
TopicViewItem.add(topic2.id, ip, target_user.id)
TopicViewItem.add(topic3.id, ip, target_user.id)
merge_users!
topic_ids = TopicViewItem.where(user_id: target_user.id).pluck(:topic_id)
expect(topic_ids).to contain_exactly(topic1.id, topic2.id, topic3.id)
expect(TopicViewItem.where(user_id: source_user.id).count).to eq(0)
end
it "updates topics" do
topic = Fabricate(:topic)
Fabricate(:post, user: walter, topic: topic)
Fabricate(:post, user: source_user, topic: topic)
topic.trash!(source_user)
merge_users!
topic.reload
expect(topic.deleted_by).to eq(target_user)
expect(topic.last_poster).to eq(target_user)
end
it "updates unsubscribe keys" do
UnsubscribeKey.create_key_for(source_user, "digest")
UnsubscribeKey.create_key_for(target_user, "digest")
UnsubscribeKey.create_key_for(walter, "digest")
merge_users!
expect(UnsubscribeKey.where(user_id: target_user.id).count).to eq(2)
expect(UnsubscribeKey.where(user_id: source_user.id).count).to eq(0)
end
it "updates uploads" do
Fabricate(:upload, user: source_user)
Fabricate(:upload, user: target_user)
Fabricate(:upload, user: walter)
merge_users!
expect(Upload.where(user_id: target_user.id).count).to eq(2)
expect(Upload.where(user_id: source_user.id).count).to eq(0)
end
context "user actions" do
# action_type and user_id are not nullable
# target_topic_id and acting_user_id are nullable, but always have a value
let(:post1) { Fabricate(:post) }
let(:post2) { Fabricate(:post) }
let(:post3) { Fabricate(:post) }
def log_pending_action(user, post)
UserAction.log_action!(action_type: UserAction::PENDING,
user_id: user.id,
acting_user_id: user.id,
target_topic_id: post.topic.id,
queued_post_id: post.id)
end
def log_like_action(acting_user, user, post)
UserAction.log_action!(action_type: UserAction::LIKE,
user_id: user.id,
acting_user_id: acting_user.id,
target_topic_id: post.topic_id,
target_post_id: post.id)
end
def log_got_private_message(acting_user, user, topic)
UserAction.log_action!(action_type: UserAction::GOT_PRIVATE_MESSAGE,
user_id: user.id,
acting_user_id: acting_user.id,
target_topic_id: topic.id,
target_post_id: -1)
end
it "merges when target_post_id is not set" do
a1 = log_pending_action(source_user, post1)
a2 = log_pending_action(source_user, post2)
a3 = log_pending_action(target_user, post2)
a4 = log_pending_action(target_user, post3)
merge_users!
expect(UserAction.count).to eq(3)
action_ids = UserAction.where(action_type: UserAction::PENDING,
user_id: target_user.id,
acting_user_id: target_user.id).pluck(:id)
expect(action_ids).to contain_exactly(a1.id, a3.id, a4.id)
end
it "merges when target_post_id is set" do
a1 = log_like_action(source_user, walter, post1)
a2 = log_like_action(target_user, walter, post1)
a3 = log_like_action(source_user, walter, post2)
merge_users!
expect(UserAction.count).to eq(2)
action_ids = UserAction.where(action_type: UserAction::LIKE,
user_id: walter.id,
acting_user_id: target_user.id).pluck(:id)
expect(action_ids).to contain_exactly(a2.id, a3.id)
end
it "merges when acting_user is neither source_user nor target_user" do
coding_horror = Fabricate(:coding_horror)
pm_topic1 = Fabricate(:private_message_topic, topic_allowed_users: [
Fabricate.build(:topic_allowed_user, user: walter),
Fabricate.build(:topic_allowed_user, user: source_user),
Fabricate.build(:topic_allowed_user, user: target_user),
Fabricate.build(:topic_allowed_user, user: coding_horror),
])
pm_topic2 = Fabricate(:private_message_topic, topic_allowed_users: [
Fabricate.build(:topic_allowed_user, user: walter),
Fabricate.build(:topic_allowed_user, user: source_user)
])
pm_topic3 = Fabricate(:private_message_topic, topic_allowed_users: [
Fabricate.build(:topic_allowed_user, user: walter),
Fabricate.build(:topic_allowed_user, user: target_user)
])
a1 = log_got_private_message(walter, source_user, pm_topic1)
a2 = log_got_private_message(walter, target_user, pm_topic1)
a3 = log_got_private_message(walter, coding_horror, pm_topic1)
a4 = log_got_private_message(walter, source_user, pm_topic2)
a5 = log_got_private_message(walter, target_user, pm_topic3)
merge_users!
expect(UserAction.count).to eq(4)
action_ids = UserAction.where(action_type: UserAction::GOT_PRIVATE_MESSAGE,
user_id: target_user.id,
acting_user_id: walter.id).pluck(:id)
expect(action_ids).to contain_exactly(a2.id, a4.id, a5.id)
end
end
it "merges archived messages" do
pm_topic1 = Fabricate(:private_message_topic, topic_allowed_users: [
Fabricate.build(:topic_allowed_user, user: target_user),
Fabricate.build(:topic_allowed_user, user: walter),
Fabricate.build(:topic_allowed_user, user: source_user)
])
pm_topic2 = Fabricate(:private_message_topic, topic_allowed_users: [
Fabricate.build(:topic_allowed_user, user: walter),
Fabricate.build(:topic_allowed_user, user: source_user)
])
UserArchivedMessage.archive!(source_user.id, pm_topic1)
UserArchivedMessage.archive!(target_user.id, pm_topic1)
UserArchivedMessage.archive!(source_user.id, pm_topic2)
UserArchivedMessage.archive!(walter.id, pm_topic2)
merge_users!
topic_ids = UserArchivedMessage.where(user_id: target_user.id).pluck(:topic_id)
expect(topic_ids).to contain_exactly(pm_topic1.id, pm_topic2.id)
expect(UserArchivedMessage.where(user_id: source_user.id).count).to eq(0)
end
context "badges" do
def create_badge(badge, user, opts = {})
UserBadge.create!(
badge: badge,
user: user,
granted_by: opts[:granted_by] || Discourse.system_user,
granted_at: opts[:granted_at] || Time.now,
post: opts[:post],
seq: opts[:seq] || 0
)
end
it "merges user badges" do
anniversary_badge = Badge.find(Badge::Anniversary)
create_badge(anniversary_badge, source_user, seq: 1)
b1 = create_badge(anniversary_badge, target_user, seq: 1)
b2 = create_badge(anniversary_badge, source_user, seq: 2)
great_post_badge = Badge.find(Badge::GreatPost)
b3 = create_badge(great_post_badge, target_user, post: Fabricate(:post, user: target_user))
b4 = create_badge(great_post_badge, source_user, post: Fabricate(:post, user: source_user))
autobiographer_badge = Badge.find(Badge::Autobiographer)
b5 = create_badge(autobiographer_badge, source_user)
merge_users!
user_badge_ids = UserBadge.where(user_id: target_user.id).pluck(:id)
expect(user_badge_ids).to contain_exactly(b1.id, b2.id, b3.id, b4.id, b5.id)
expect(UserBadge.where(user_id: source_user.id).count).to eq(0)
end
it "updates granted_by for user badges" do
badge = Badge.create!(name: 'Hero', badge_type_id: BadgeType::Gold)
user_badge = create_badge(badge, walter, seq: 1, granted_by: source_user)
merge_users!
expect(user_badge.reload.granted_by).to eq(target_user)
end
end
it "merges user custom fields" do
UserCustomField.create!(user_id: source_user.id, name: 'foo', value: '123')
UserCustomField.create!(user_id: source_user.id, name: 'bar', value: '456')
UserCustomField.create!(user_id: source_user.id, name: 'duplicate', value: 'source')
UserCustomField.create!(user_id: target_user.id, name: 'duplicate', value: 'target')
UserCustomField.create!(user_id: target_user.id, name: 'baz', value: '789')
merge_users!
fields = UserCustomField.where(user_id: target_user.id).pluck(:name, :value)
expect(fields).to contain_exactly(['foo', '123'], ['bar', '456'], ['duplicate', 'target'], ['baz', '789'])
expect(UserCustomField.where(user_id: source_user.id).count).to eq(0)
end
it "merges email addresses" do
merge_users!
emails = UserEmail.where(user_id: target_user.id).pluck(:email, :primary)
expect(emails).to contain_exactly(['alice@example.com', true], ['alice@work.com', false])
expect(UserEmail.where(user_id: source_user.id).count).to eq(0)
end
it "skips merging email adresses when a secondary email address exists" do
merge_users!(source_user, target_user)
alice2 = Fabricate(:user_single_email, username: 'alice2', email: 'alice@foo.com')
merge_users!(alice2, target_user)
emails = UserEmail.where(user_id: target_user.id).pluck(:email, :primary)
expect(emails).to contain_exactly(['alice@example.com', true], ['alice@work.com', false])
expect(UserEmail.where(user_id: source_user.id).count).to eq(0)
end
it "updates exports" do
UserExport.create(file_name: "user-archive-alice1-190218-003249", user_id: source_user.id)
merge_users!
expect(UserExport.where(user_id: target_user.id).count).to eq(1)
expect(UserExport.where(user_id: source_user.id).count).to eq(0)
end
it "updates user history" do
UserHistory.create(action: UserHistory.actions[:notified_about_get_a_room], target_user_id: source_user.id)
UserHistory.create(action: UserHistory.actions[:anonymize_user], target_user_id: walter.id, acting_user_id: source_user.id)
merge_users!
expect(UserHistory.where(target_user_id: target_user.id).count).to eq(1)
expect(UserHistory.where(target_user_id: source_user.id).count).to eq(0)
expect(UserHistory.where(acting_user_id: target_user.id).count).to eq(1)
expect(UserHistory.where(acting_user_id: source_user.id).count).to eq(0)
end
it "updates user profile views" do
ip = '127.0.0.1'
UserProfileView.add(source_user.id, ip, walter.id, Time.now, true)
UserProfileView.add(source_user.id, ip, target_user.id, Time.now, true)
UserProfileView.add(target_user.id, ip, source_user.id, Time.now, true)
UserProfileView.add(walter.id, ip, source_user.id, Time.now, true)
merge_users!
expect(UserProfileView.where(user_profile_id: target_user.id).count).to eq(3)
expect(UserProfileView.where(user_profile_id: walter.id).count).to eq(1)
expect(UserProfileView.where(user_profile_id: source_user.id).count).to eq(0)
expect(UserProfileView.where(user_id: target_user.id).count).to eq(3)
expect(UserProfileView.where(user_id: walter.id).count).to eq(1)
expect(UserProfileView.where(user_id: source_user.id).count).to eq(0)
end
it "merges user visits" do
freeze_time DateTime.parse('2010-01-01 12:00')
UserVisit.create!(user_id: source_user.id, visited_at: 2.days.ago, posts_read: 22, mobile: false, time_read: 400)
UserVisit.create!(user_id: source_user.id, visited_at: Date.yesterday, posts_read: 8, mobile: false, time_read: 100)
UserVisit.create!(user_id: target_user.id, visited_at: Date.yesterday, posts_read: 12, mobile: true, time_read: 270)
UserVisit.create!(user_id: target_user.id, visited_at: Date.today, posts_read: 10, mobile: true, time_read: 150)
merge_users!
expect(UserVisit.where(user_id: target_user.id).count).to eq(3)
expect(UserVisit.where(user_id: source_user.id).count).to eq(0)
expect(UserVisit.where(user_id: target_user.id, visited_at: 2.days.ago, posts_read: 22, mobile: false, time_read: 400).count).to eq(1)
expect(UserVisit.where(user_id: target_user.id, visited_at: Date.yesterday, posts_read: 20, mobile: true, time_read: 370).count).to eq(1)
expect(UserVisit.where(user_id: target_user.id, visited_at: Date.today, posts_read: 10, mobile: true, time_read: 150).count).to eq(1)
end
it "updates user warnings" do
UserWarning.create!(topic: Fabricate(:topic), user: source_user, created_by: walter)
UserWarning.create!(topic: Fabricate(:topic), user: target_user, created_by: walter)
UserWarning.create!(topic: Fabricate(:topic), user: walter, created_by: source_user)
merge_users!
expect(UserWarning.where(user_id: target_user.id).count).to eq(2)
expect(UserWarning.where(user_id: source_user.id).count).to eq(0)
expect(UserWarning.where(created_by_id: target_user.id).count).to eq(1)
expect(UserWarning.where(created_by_id: source_user.id).count).to eq(0)
end
it "triggers :merging_users event" do
events = DiscourseEvent.track_events do
merge_users!
end
expect(events).to include(event_name: :merging_users, params: [source_user, target_user])
end
context "site settings" do
it "updates usernames in site settings" do
SiteSetting.site_contact_username = source_user.username
SiteSetting.embed_by_username = source_user.username
merge_users!
expect(SiteSetting.site_contact_username).to eq(target_user.username)
expect(SiteSetting.embed_by_username).to eq(target_user.username)
end
it "updates only the old username in site settings" do
SiteSetting.site_contact_username = source_user.username
SiteSetting.embed_by_username = walter.username
merge_users!
expect(SiteSetting.site_contact_username).to eq(target_user.username)
expect(SiteSetting.embed_by_username).to eq(walter.username)
end
end
it "updates users" do
walter.update_attribute(:approved_by, source_user)
merge_users!
expect(walter.reload.approved_by).to eq(target_user)
end
it "deletes the source user even when it's an admin" do
source_user.update_attribute(:admin, true)
expect(User.find_by_username(source_user.username)).to be_present
merge_users!
expect(User.find_by_username(source_user.username)).to be_nil
end
it "deletes the source user even when it is a member of a group that grants a trust level" do
group = Fabricate(:group, grant_trust_level: 3)
group.bulk_add([source_user.id, target_user.id])
merge_users!
expect(User.find_by_username(source_user.username)).to be_nil
end
it "deletes external auth infos of source user" do
FacebookUserInfo.create(user_id: source_user.id, facebook_user_id: "example")
GithubUserInfo.create(user_id: source_user.id, screen_name: "example", github_user_id: "examplel123123")
GoogleUserInfo.create(user_id: source_user.id, google_user_id: "google@gmail.com")
InstagramUserInfo.create(user_id: source_user.id, screen_name: "example", instagram_user_id: "examplel123123")
Oauth2UserInfo.create(user_id: source_user.id, uid: "example", provider: "example")
SingleSignOnRecord.create(user_id: source_user.id, external_id: "example", last_payload: "looks good")
TwitterUserInfo.create(user_id: source_user.id, screen_name: "example", twitter_user_id: "examplel123123")
UserOpenId.create(user_id: source_user.id, email: source_user.email, url: "http://example.com/openid", active: true)
merge_users!
expect(FacebookUserInfo.where(user_id: source_user.id).count).to eq(0)
expect(GithubUserInfo.where(user_id: source_user.id).count).to eq(0)
expect(GoogleUserInfo.where(user_id: source_user.id).count).to eq(0)
expect(InstagramUserInfo.where(user_id: source_user.id).count).to eq(0)
expect(Oauth2UserInfo.where(user_id: source_user.id).count).to eq(0)
expect(SingleSignOnRecord.where(user_id: source_user.id).count).to eq(0)
expect(TwitterUserInfo.where(user_id: source_user.id).count).to eq(0)
expect(UserOpenId.where(user_id: source_user.id).count).to eq(0)
end
it "deletes auth tokens" do
Fabricate(:api_key, user: source_user)
Fabricate(:readonly_user_api_key, user: source_user)
Fabricate(:user_second_factor, user: source_user)
SiteSetting.verbose_auth_token_logging = true
UserAuthToken.generate!(user_id: source_user.id, user_agent: "Firefox", client_ip: "127.0.0.1")
merge_users!
expect(ApiKey.where(user_id: source_user.id).count).to eq(0)
expect(UserApiKey.where(user_id: source_user.id).count).to eq(0)
expect(UserSecondFactor.where(user_id: source_user.id).count).to eq(0)
expect(UserAuthToken.where(user_id: source_user.id).count).to eq(0)
expect(UserAuthTokenLog.where(user_id: source_user.id).count).to eq(0)
end
it "cleans up all remaining references to the source user" do
DirectoryItem.refresh!
Fabricate(:email_change_request, user: source_user)
Fabricate(:email_token, user: source_user)
Fabricate(:user_avatar, user: source_user)
merge_users!
expect(DirectoryItem.where(user_id: source_user.id).count).to eq(0)
expect(EmailChangeRequest.where(user_id: source_user.id).count).to eq(0)
expect(EmailToken.where(user_id: source_user.id).count).to eq(0)
expect(UserAvatar.where(user_id: source_user.id).count).to eq(0)
expect(User.find_by_username(source_user.username)).to be_nil
end
it "updates the username" do
Jobs::UpdateUsername.any_instance
.expects(:execute)
.with(user_id: source_user.id,
old_username: 'alice1',
new_username: 'alice',
avatar_template: target_user.avatar_template)
.once
merge_users!
end
end