discourse/spec/services/user_merger_spec.rb
Martin Brennan e62e93f83a
FEATURE: Introduce personal_message_enabled_groups setting (#18042)
This will replace `enable_personal_messages` and
`min_trust_to_send_messages`, this commit introduces
the setting `personal_message_enabled_groups`
and uses it in all places that `enable_personal_messages`
and `min_trust_to_send_messages` currently apply.

A migration is included to set `personal_message_enabled_groups`
based on the following rules:

* If `enable_personal_messages` was false, then set
  `personal_message_enabled_groups` to `3`, which is
  the staff auto group
* If `min_trust_to_send_messages` is not default (1)
  and the above condition is false, then set the
  `personal_message_enabled_groups` setting to
  the appropriate auto group based on the trust level
* Otherwise just set `personal_message_enabled_groups` to
  11 which is the TL1 auto group

After follow-up PRs to plugins using these old settings, we will be
able to drop the old settings from core, in the meantime I've added
 DEPRECATED notices to their descriptions and added them
to the deprecated site settings list.

This commit also introduces a `_map` shortcut method definition
for all `group_list` site settings, e.g. `SiteSetting.personal_message_enabled_groups`
also has `SiteSetting.personal_message_enabled_groups_map` available,
which automatically splits the setting by `|` and converts it into
an array of integers.
2022-09-26 13:58:40 +10:00

1072 lines
39 KiB
Ruby

# frozen_string_literal: true
RSpec.describe UserMerger do
fab!(:target_user) { Fabricate(:user, username: 'alice', email: 'alice@example.com') }
fab!(:source_user) { Fabricate(:user, username: 'alice1', email: 'alice@work.com') }
fab!(:walter) { Fabricate(:walter_white) }
fab!(:coding_horror) { Fabricate(:coding_horror) }
fab!(:p1) { Fabricate(:post) }
fab!(:p2) { Fabricate(:post) }
fab!(:p3) { Fabricate(:post) }
fab!(:p4) { Fabricate(:post) }
fab!(:p5) { Fabricate(:post) }
fab!(:p6) { Fabricate(:post) }
before do
Group.refresh_automatic_groups!
end
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 "with 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 "with 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 "with 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
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_first(:user_count)).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)
Fabricate(:invited_user, invite: invite1, 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.invited_users.first.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)
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)
Fabricate(:ignored_user, user: source_user, ignored_user: ignored1)
Fabricate(:ignored_user, user: source_user, ignored_user: ignored2)
Fabricate(:ignored_user, user: target_user, ignored_user: ignored2)
Fabricate(:ignored_user, user: target_user, ignored_user: ignored3)
Fabricate(:ignored_user, user: walter, ignored_user: source_user)
Fabricate(:ignored_user, user: coding_horror, ignored_user: source_user)
Fabricate(:ignored_user, user: coding_horror, ignored_user: target_user)
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 "with 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 "with post actions" do
it "merges post actions" do
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
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 = p1
post_revision = Fabricate(:post_revision, post: post, user: source_user)
merge_users!
expect(post_revision.reload.user).to eq(target_user)
end
context "with 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 = p1
post2 = p2
post3 = p3
post4 = p4
create_post_timing(post1, source_user, 12345)
create_post_timing(post2, source_user, 9876)
create_post_timing(post4, source_user, 2**31 - 100)
create_post_timing(post2, target_user, 3333)
create_post_timing(post3, target_user, 10000)
create_post_timing(post4, target_user, 5000)
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(post_timing_msecs_for(post4, target_user)).to eq(2**31 - 1)
expect(PostTiming.where(user_id: source_user.id).count).to eq(0)
end
end
context "with 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.public_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 "with 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, UnsubscribeKey::DIGEST_TYPE)
UnsubscribeKey.create_key_for(target_user, UnsubscribeKey::DIGEST_TYPE)
UnsubscribeKey.create_key_for(walter, UnsubscribeKey::DIGEST_TYPE)
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 "with 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
fab!(:post1) { p1 }
fab!(:post2) { p2 }
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
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 "with 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 addresses 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 "skips merging email addresses when target user is not human" do
target_user = Discourse.system_user
merge_users!(source_user, target_user)
emails = UserEmail.where(user_id: target_user.id).pluck(:email, :primary)
expect(emails).to contain_exactly([target_user.email, true])
expect(UserEmail.exists?(user_id: source_user.id)).to eq(false)
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 "with 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 "works even when email domains are restricted" do
SiteSetting.allowed_email_domains = "example.com|work.com"
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 external auth infos of source user" do
UserAssociatedAccount.create(user_id: source_user.id, provider_name: "facebook", provider_uid: "1234")
SingleSignOnRecord.create(user_id: source_user.id, external_id: "example", last_payload: "looks good")
merge_users!
expect(UserAssociatedAccount.where(user_id: source_user.id).count).to eq(0)
expect(SingleSignOnRecord.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