discourse/spec/services/user_merger_spec.rb
Guo Xiang Tan 24347ace10 FIX: Properly associate user_profiles background urls via upload id.
`Upload#url` is more likely and can change from time to time. When it
does changes, we don't want to have to look through multiple tables to
ensure that the URLs are all up to date. Instead, we simply associate
uploads properly to `UserProfile` so that it does not have to replicate
the URLs in the table.
2019-05-02 14:58:24 +08:00

1064 lines
39 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
describe UserMerger do
let!(:target_user) { Fabricate(:user, username: 'alice', email: 'alice@example.com') }
let!(:source_user) { Fabricate(:user, 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, 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)
])
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)
small1 = pm_topic.add_small_action(source_user, "invited_user", "carol")
small2 = pm_topic.add_small_action(target_user, "invited_user", "david")
small3 = pm_topic.add_small_action(walter, "invited_user", "eve")
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)
expect(small1.reload.user).to eq(target_user)
expect(small2.reload.user).to eq(target_user)
expect(small3.reload.user).to eq(walter)
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)
PostActionCreator.like(source_user, p1)
PostActionCreator.like(source_user, p2)
PostActionCreator.like(target_user, p2)
PostActionCreator.like(target_user, p3)
freeze_time(now)
PostActionCreator.like(source_user, p4)
PostActionCreator.like(source_user, p5)
PostActionCreator.like(target_user, p5)
PostActionCreator.like(source_user, p6)
PostActionDestroyer.destroy(source_user, p6, :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
it "merges ignored users" do
ignored1 = Fabricate(:user)
ignored2 = Fabricate(:user)
ignored3 = Fabricate(:user)
coding_horror = Fabricate(:coding_horror)
IgnoredUser.create!(user_id: source_user.id, ignored_user_id: ignored1.id)
IgnoredUser.create!(user_id: source_user.id, ignored_user_id: ignored2.id)
IgnoredUser.create!(user_id: target_user.id, ignored_user_id: ignored2.id)
IgnoredUser.create!(user_id: target_user.id, ignored_user_id: ignored3.id)
IgnoredUser.create!(user_id: walter.id, ignored_user_id: source_user.id)
IgnoredUser.create!(user_id: coding_horror.id, ignored_user_id: source_user.id)
IgnoredUser.create!(user_id: coding_horror.id, ignored_user_id: target_user.id)
merge_users!
[ignored1, ignored2, ignored3].each do |m|
expect(IgnoredUser.where(user_id: target_user.id, ignored_user_id: m.id).count).to eq(1)
end
expect(IgnoredUser.where(user_id: source_user.id).count).to eq(0)
expect(IgnoredUser.where(user_id: walter.id, ignored_user_id: target_user.id).count).to eq(1)
expect(IgnoredUser.where(user_id: coding_horror.id, ignored_user_id: target_user.id).count).to eq(1)
expect(IgnoredUser.where(ignored_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|
PostActionCreator.new(source_user, p1, type).perform
PostActionCreator.new(source_user, p2, type).perform
PostActionCreator.new(target_user, p2, type).perform
PostActionCreator.new(target_user, p3, type).perform
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 = PostActionCreator.create(source_user, p1, :off_topic).post_action
action1.update_attribute(:deleted_by_id, source_user.id)
action2 = PostActionCreator.create(source_user, p2, :off_topic).post_action
action2.update_attribute(:deferred_by_id, source_user.id)
action3 = PostActionCreator.create(source_user, p3, :off_topic).post_action
action3.update_attribute(:agreed_by_id, source_user.id)
action4 = PostActionCreator.create(source_user, p4, :off_topic).post_action
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)
PostActionCreator.new(source_user, post, type_id).perform
PostActionCreator.new(target_user, post, type_id).perform
end
merge_users!
posts.each do |type, post|
post.reload
expect(post.send("#{type}_count")).to eq(1)
end
end
end
it "updates reviewables and reviewable history" do
reviewable = Fabricate(:reviewable_queued_post, created_by: source_user)
merge_users!
expect(reviewable.reload.created_by).to eq(target_user)
expect(reviewable.reviewable_histories.first.created_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 = Fabricate(:theme, user: source_user)
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) }
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 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, 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!
UserHistory.where(action: UserHistory.actions[:merge_user], target_user_id: target_user.id).delete_all
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!(approved_by: source_user)
upload = Fabricate(:upload)
source_user.update!(admin: true)
source_user.user_profile.update!(
card_background_upload: upload,
profile_background_upload: upload,
)
merge_users!
expect(walter.reload.approved_by).to eq(target_user)
target_user.reload
expect(target_user.admin).to eq(true)
expect(target_user.card_background_upload).to eq(upload)
expect(target_user.profile_background_upload).to eq(upload)
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
UserAssociatedAccount.create(user_id: source_user.id, provider_name: "facebook", provider_uid: "1234")
GithubUserInfo.create(user_id: source_user.id, screen_name: "example", github_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")
UserOpenId.create(user_id: source_user.id, email: source_user.email, url: "http://example.com/openid", active: true)
merge_users!
expect(UserAssociatedAccount.where(user_id: source_user.id).count).to eq(0)
expect(GithubUserInfo.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(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_totp, 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
it "correctly logs the merge" do
expect { merge_users! }.to change { UserHistory.count }.by(1)
log_entry = UserHistory.last
expect(log_entry.action).to eq(UserHistory.actions[:merge_user])
expect(log_entry.acting_user_id).to eq(Discourse::SYSTEM_USER_ID)
expect(log_entry.target_user_id).to eq(target_user.id)
expect(log_entry.context).to eq(I18n.t("staff_action_logs.user_merged", username: source_user.username))
expect(log_entry.email).to eq("alice@work.com")
end
end