FEATURE: rake task for merging users

This commit is contained in:
Gerhard Schlager 2018-02-28 22:21:52 +01:00
parent fffd1a6602
commit 7a2183e8ab
6 changed files with 1632 additions and 10 deletions

434
app/services/user_merger.rb Normal file
View File

@ -0,0 +1,434 @@
class UserMerger
def initialize(source_user, target_user)
@source_user = source_user
@target_user = target_user
end
def merge!
update_notifications
move_posts
update_user_ids
merge_given_daily_likes
merge_post_timings
merge_user_visits
update_site_settings
merge_user_attributes
DiscourseEvent.trigger(:merging_users, @source_user, @target_user)
update_user_stats
delete_source_user
delete_source_user_references
end
protected
def update_notifications
params = {
source_user_id: @source_user.id,
source_username: @source_user.username,
target_username: @target_user.username,
notification_types_with_correct_user_id: [
Notification.types[:granted_badge],
Notification.types[:group_message_summary]
],
invitee_accepted_notification_type: Notification.types[:invitee_accepted]
}
Notification.exec_sql(<<~SQL, params)
UPDATE notifications AS n
SET data = (data :: JSONB ||
jsonb_strip_nulls(
jsonb_build_object(
'original_username', CASE data :: JSONB ->> 'original_username'
WHEN :source_username
THEN :target_username
ELSE NULL END,
'display_username', CASE data :: JSONB ->> 'display_username'
WHEN :source_username
THEN :target_username
ELSE NULL END,
'username', CASE data :: JSONB ->> 'username'
WHEN :source_username
THEN :target_username
ELSE NULL END
)
)) :: JSON
WHERE EXISTS(
SELECT 1
FROM posts AS p
WHERE p.topic_id = n.topic_id
AND p.post_number = n.post_number
AND p.user_id = :source_user_id)
OR (n.notification_type IN (:notification_types_with_correct_user_id) AND n.user_id = :source_user_id)
OR (n.notification_type = :invitee_accepted_notification_type
AND EXISTS(
SELECT 1
FROM invites i
WHERE i.user_id = :source_user_id AND n.user_id = i.invited_by_id
)
)
SQL
end
def move_posts
posts = Post.with_deleted
.where(user_id: @source_user.id)
.order(:topic_id, :post_number)
.pluck(:topic_id, :id)
last_topic_id = nil
post_ids = []
posts.each do |current_topic_id, current_post_id|
if last_topic_id != current_topic_id && post_ids.any?
change_post_owner(last_topic_id, post_ids)
post_ids = []
end
last_topic_id = current_topic_id
post_ids << current_post_id
end
change_post_owner(last_topic_id, post_ids) if post_ids.any?
end
def change_post_owner(topic_id, post_ids)
PostOwnerChanger.new(
topic_id: topic_id,
post_ids: post_ids,
new_owner: @target_user,
acting_user: Discourse.system_user,
skip_revision: true
).change_owner!
end
def merge_given_daily_likes
sql = <<~SQL
INSERT INTO given_daily_likes AS g (user_id, likes_given, given_date, limit_reached)
SELECT
:target_user_id AS user_id,
COUNT(1) AS likes_given,
a.created_at::DATE AS given_date,
COUNT(1) >= :max_likes_per_day AS limit_reached
FROM post_actions AS a
WHERE a.user_id = :target_user_id
AND a.deleted_at IS NULL
AND EXISTS(
SELECT 1
FROM given_daily_likes AS g
WHERE g.user_id = :source_user_id AND a.created_at::DATE = g.given_date
)
GROUP BY given_date
ON CONFLICT (user_id, given_date)
DO UPDATE
SET likes_given = EXCLUDED.likes_given,
limit_reached = EXCLUDED.limit_reached
SQL
GivenDailyLike.exec_sql(sql,
source_user_id: @source_user.id,
target_user_id: @target_user.id,
max_likes_per_day: SiteSetting.max_likes_per_day,
action_type_id: PostActionType.types[:like])
end
def merge_post_timings
update_user_id(:post_timings, conditions: ["x.topic_id = y.topic_id",
"x.post_number = y.post_number"])
sql = <<~SQL
UPDATE post_timings AS t
SET msecs = t.msecs + s.msecs
FROM post_timings AS s
WHERE t.user_id = :target_user_id AND s.user_id = :source_user_id
AND t.topic_id = s.topic_id AND t.post_number = s.post_number
SQL
PostTiming.exec_sql(sql, source_user_id: @source_user.id, target_user_id: @target_user.id)
end
def merge_user_visits
update_user_id(:user_visits, conditions: "x.visited_at = y.visited_at")
sql = <<~SQL
UPDATE user_visits AS t
SET posts_read = t.posts_read + s.posts_read,
mobile = t.mobile OR s.mobile,
time_read = t.time_read + s.time_read
FROM user_visits AS s
WHERE t.user_id = :target_user_id AND s.user_id = :source_user_id
AND t.visited_at = s.visited_at
SQL
UserVisit.exec_sql(sql, source_user_id: @source_user.id, target_user_id: @target_user.id)
end
def update_site_settings
SiteSetting.all_settings(true).each do |setting|
if setting[:type] == "username" && setting[:value] == @source_user.username
SiteSetting.set_and_log(setting[:setting], @target_user.username)
end
end
end
def update_user_stats
# topics_entered
UserStat.exec_sql(<<~SQL, target_user_id: @target_user.id)
UPDATE user_stats
SET topics_entered = (
SELECT COUNT(topic_id)
FROM topic_views
WHERE user_id = :target_user_id
)
WHERE user_id = :target_user_id
SQL
# time_read and days_visited
UserStat.exec_sql(<<~SQL, target_user_id: @target_user.id)
UPDATE user_stats
SET time_read = COALESCE(x.time_read, 0),
days_visited = COALESCE(x.days_visited, 0)
FROM (
SELECT
SUM(time_read) AS time_read,
COUNT(1) AS days_visited
FROM user_visits
WHERE user_id = :target_user_id
) AS x
WHERE user_id = :target_user_id
SQL
# posts_read_count
UserStat.exec_sql(<<~SQL, target_user_id: @target_user.id)
UPDATE user_stats
SET posts_read_count = (
SELECT COUNT(1)
FROM post_timings AS pt
WHERE pt.user_id = :target_user_id AND EXISTS(
SELECT 1
FROM topics AS t
WHERE t.archetype = 'regular' AND t.deleted_at IS NULL
))
WHERE user_id = :target_user_id
SQL
# likes_given, likes_received, new_since, read_faq, first_post_created_at
UserStat.exec_sql(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id)
UPDATE user_stats AS t
SET likes_given = t.likes_given + s.likes_given,
likes_received = t.likes_received + s.likes_received,
new_since = LEAST(t.new_since, s.new_since),
read_faq = LEAST(t.read_faq, s.read_faq),
first_post_created_at = LEAST(t.first_post_created_at, s.first_post_created_at)
FROM user_stats AS s
WHERE t.user_id = :target_user_id AND s.user_id = :source_user_id
SQL
end
def merge_user_attributes
User.exec_sql(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id)
UPDATE users AS t
SET created_at = LEAST(t.created_at, s.created_at),
updated_at = LEAST(t.updated_at, s.updated_at),
seen_notification_id = GREATEST(t.seen_notification_id, s.seen_notification_id),
last_posted_at = GREATEST(t.last_seen_at, s.last_seen_at),
last_seen_at = GREATEST(t.last_seen_at, s.last_seen_at),
admin = t.admin OR s.admin,
last_emailed_at = GREATEST(t.last_emailed_at, s.last_emailed_at),
trust_level = GREATEST(t.trust_level, s.trust_level),
previous_visit_at = GREATEST(t.previous_visit_at, s.previous_visit_at),
date_of_birth = COALESCE(t.date_of_birth, s.date_of_birth),
ip_address = COALESCE(t.ip_address, s.ip_address),
moderator = t.moderator OR s.moderator,
title = COALESCE(t.title, s.title),
primary_group_id = COALESCE(t.primary_group_id, s.primary_group_id),
registration_ip_address = COALESCE(t.registration_ip_address, s.registration_ip_address),
first_seen_at = LEAST(t.first_seen_at, s.first_seen_at),
group_locked_trust_level = GREATEST(t.group_locked_trust_level, s.group_locked_trust_level),
manual_locked_trust_level = GREATEST(t.manual_locked_trust_level, s.manual_locked_trust_level)
FROM users AS s
WHERE t.id = :target_user_id AND s.id = :source_user_id
SQL
UserProfile.exec_sql(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id)
UPDATE user_profiles AS t
SET location = COALESCE(t.location, s.location),
website = COALESCE(t.website, s.website),
bio_raw = COALESCE(t.bio_raw, s.bio_raw),
bio_cooked = COALESCE(t.bio_cooked, s.bio_cooked),
bio_cooked_version = COALESCE(t.bio_cooked_version, s.bio_cooked_version),
profile_background = COALESCE(t.profile_background, s.profile_background),
dismissed_banner_key = COALESCE(t.dismissed_banner_key, s.dismissed_banner_key),
badge_granted_title = t.badge_granted_title OR s.badge_granted_title,
card_background = COALESCE(t.card_background, s.card_background),
card_image_badge_id = COALESCE(t.card_image_badge_id, s.card_image_badge_id),
views = t.views + s.views
FROM user_profiles AS s
WHERE t.user_id = :target_user_id AND s.user_id = :source_user_id
SQL
end
def update_user_ids
Category.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
update_user_id(:category_users, conditions: ["x.category_id = y.category_id"])
update_user_id(:developers)
update_user_id(:draft_sequences, conditions: "x.draft_key = y.draft_key")
update_user_id(:drafts, conditions: "x.draft_key = y.draft_key")
EmailLog.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
GroupHistory.where(acting_user_id: @source_user.id).update_all(acting_user_id: @target_user.id)
GroupHistory.where(target_user_id: @source_user.id).update_all(target_user_id: @target_user.id)
update_user_id(:group_users, conditions: "x.group_id = y.group_id")
IncomingEmail.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
IncomingLink.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
IncomingLink.where(current_user_id: @source_user.id).update_all(current_user_id: @target_user.id)
Invite.with_deleted.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
Invite.with_deleted.where(invited_by_id: @source_user.id).update_all(invited_by_id: @target_user.id)
Invite.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id)
update_user_id(:muted_users, conditions: "x.muted_user_id = y.muted_user_id")
update_user_id(:muted_users, user_id_column_name: "muted_user_id", conditions: "x.user_id = y.user_id")
Notification.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
update_user_id(:post_actions, conditions: ["x.post_id = y.post_id",
"x.post_action_type_id = y.post_action_type_id",
"x.targets_topic = y.targets_topic"])
PostAction.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id)
PostAction.where(deferred_by_id: @source_user.id).update_all(deferred_by_id: @target_user.id)
PostAction.where(agreed_by_id: @source_user.id).update_all(agreed_by_id: @target_user.id)
PostAction.where(disagreed_by_id: @source_user.id).update_all(disagreed_by_id: @target_user.id)
PostRevision.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
Post.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id)
Post.with_deleted.where(last_editor_id: @source_user.id).update_all(last_editor_id: @target_user.id)
Post.with_deleted.where(locked_by_id: @source_user.id).update_all(locked_by_id: @target_user.id)
Post.with_deleted.where(reply_to_user_id: @source_user.id).update_all(reply_to_user_id: @target_user.id)
QueuedPost.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
QueuedPost.where(approved_by_id: @source_user.id).update_all(approved_by_id: @target_user.id)
QueuedPost.where(rejected_by_id: @source_user.id).update_all(rejected_by_id: @target_user.id)
SearchLog.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
update_user_id(:tag_users, conditions: "x.tag_id = y.tag_id")
Theme.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
update_user_id(:topic_allowed_users, conditions: "x.topic_id = y.topic_id")
TopicEmbed.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id)
TopicLink.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
TopicLinkClick.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
TopicTimer.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id)
update_user_id(:topic_timers, conditions: ["x.status_type = y.status_type",
"x.topic_id = y.topic_id",
"y.deleted_at IS NULL"])
update_user_id(:topic_users, conditions: "x.topic_id = y.topic_id")
update_user_id(:topic_views, conditions: "x.topic_id = y.topic_id")
Topic.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id)
UnsubscribeKey.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
Upload.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
update_user_id(:user_archived_messages, conditions: "x.topic_id = y.topic_id")
update_user_id(:user_actions,
user_id_column_name: "user_id",
conditions: ["x.action_type = y.action_type",
"x.target_topic_id IS NOT DISTINCT FROM y.target_topic_id",
"x.target_post_id IS NOT DISTINCT FROM y.target_post_id",
"x.acting_user_id IN (:source_user_id, :target_user_id)"])
update_user_id(:user_actions,
user_id_column_name: "acting_user_id",
conditions: ["x.action_type = y.action_type",
"x.user_id = y.user_id",
"x.target_topic_id IS NOT DISTINCT FROM y.target_topic_id",
"x.target_post_id IS NOT DISTINCT FROM y.target_post_id"])
update_user_id(:user_badges, conditions: ["x.badge_id = y.badge_id",
"x.seq = y.seq",
"x.post_id IS NOT DISTINCT FROM y.post_id"])
UserBadge.where(granted_by_id: @source_user.id).update_all(granted_by_id: @target_user.id)
update_user_id(:user_custom_fields, conditions: "x.name = y.name")
update_user_id(:user_emails, conditions: "x.email = y.email", updates: '"primary" = false')
UserExport.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
UserHistory.where(target_user_id: @source_user.id).update_all(target_user_id: @target_user.id)
UserHistory.where(acting_user_id: @source_user.id).update_all(acting_user_id: @target_user.id)
UserProfileView.where(user_profile_id: @source_user.id).update_all(user_profile_id: @target_user.id)
UserProfileView.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
UserWarning.where(user_id: @source_user.id).update_all(user_id: @target_user.id)
UserWarning.where(created_by_id: @source_user.id).update_all(created_by_id: @target_user.id)
User.where(approved_by_id: @source_user.id).update_all(approved_by_id: @target_user.id)
end
def delete_source_user
@source_user.reload
@source_user.update_attribute(:admin, false)
UserDestroyer.new(Discourse.system_user).destroy(@source_user)
end
def delete_source_user_references
Developer.where(user_id: @source_user.id).delete_all
DraftSequence.where(user_id: @source_user.id).delete_all
GivenDailyLike.where(user_id: @source_user.id).delete_all
MutedUser.where(user_id: @source_user.id).or(MutedUser.where(muted_user_id: @source_user.id)).delete_all
UserAuthTokenLog.where(user_id: @source_user.id).delete_all
UserAvatar.where(user_id: @source_user.id).delete_all
UserAction.where(acting_user_id: @source_user.id).delete_all
end
def update_user_id(table_name, opts = {})
builder = update_user_id_sql_builder(table_name, opts)
builder.exec(source_user_id: @source_user.id, target_user_id: @target_user.id)
end
def update_user_id_sql_builder(table_name, opts = {})
user_id_column_name = opts[:user_id_column_name] || :user_id
conditions = Array.wrap(opts[:conditions])
updates = Array.wrap(opts[:updates])
builder = SqlBuilder.new(<<~SQL)
UPDATE #{table_name} AS x
/*set*/
WHERE x.#{user_id_column_name} = :source_user_id AND NOT EXISTS(
SELECT 1
FROM #{table_name} AS y
/*where*/
)
SQL
builder.set("#{user_id_column_name} = :target_user_id")
updates.each { |u| builder.set(u) }
builder.where("y.#{user_id_column_name} = :target_user_id")
conditions.each { |c| builder.where(c) }
builder
end
end

View File

@ -4,20 +4,14 @@ task "users:change_post_ownership", [:old_username, :new_username, :archetype] =
new_username = args[:new_username]
archetype = args[:archetype]
archetype = archetype.downcase if archetype
if !old_username || !new_username
puts "ERROR: Expecting rake posts:change_post_ownership[old_username,new_username,archetype]"
exit 1
end
old_user = User.find_by(username_lower: old_username.downcase)
if !old_user
puts "ERROR: User with username #{old_username} does not exist"
exit 1
end
new_user = User.find_by(username_lower: new_username.downcase)
if !new_user
puts "ERROR: User with username #{new_username} does not exist"
exit 1
end
old_user = find_user(old_username)
new_user = find_user(new_username)
if archetype == "private"
posts = Post.private_posts.where(user_id: old_user.id)
@ -37,3 +31,30 @@ task "users:change_post_ownership", [:old_username, :new_username, :archetype] =
end
puts "", "#{i} posts ownership changed!", ""
end
task "users:merge", [:source_username, :target_username] => [:environment] do |_, args|
source_username = args[:source_username]
target_username = args[:target_username]
if !source_username || !target_username
puts "ERROR: Expecting rake posts:merge[source_username,target_username]"
exit 1
end
source_user = find_user(source_username)
target_user = find_user(target_username)
UserMerger.new(source_user, target_user).merge!
puts "", "Users merged!", ""
end
def find_user(username)
user = User.find_by_username(username)
if !user
puts "ERROR: User with username #{username} does not exist"
exit 1
end
user
end

View File

@ -0,0 +1,59 @@
module DiscoursePoll
class VotesUpdater
def self.merge_users!(source_user, target_user)
post_ids = PostCustomField.where(name: DiscoursePoll::VOTES_CUSTOM_FIELD)
.where("value :: JSON -> ? IS NOT NULL", source_user.id.to_s)
.pluck(:post_id)
post_ids.each do |post_id|
DistributedMutex.synchronize("#{DiscoursePoll::MUTEX_PREFIX}-#{post_id}") do
post = Post.find_by(id: post_id)
update_votes(post, source_user, target_user) if post
end
end
end
def self.update_votes(post, source_user, target_user)
polls = post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]
votes = post.custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD]
return if polls.nil? || votes.nil? || !votes.has_key?(source_user.id.to_s)
if votes.has_key?(target_user.id.to_s)
remove_votes(polls, votes, source_user)
else
replace_voter_id(polls, votes, source_user, target_user)
end
post.save_custom_fields(true)
end
def self.remove_votes(polls, votes, source_user)
votes.delete(source_user.id.to_s).each do |poll_name, option_ids|
poll = polls[poll_name]
next unless poll && option_ids
poll["options"].each do |option|
if option_ids.include?(option["id"])
option["votes"] -= 1
voter_ids = option["voter_ids"]
voter_ids.delete(source_user.id) if voter_ids
end
end
end
end
def self.replace_voter_id(polls, votes, source_user, target_user)
votes[target_user.id.to_s] = votes.delete(source_user.id.to_s)
polls.each_value do |poll|
next unless poll["public"] == "true"
poll["options"].each do |option|
voter_ids = option["voter_ids"]
voter_ids << target_user.id if voter_ids&.delete(source_user.id)
end
end
end
end
end

View File

@ -18,10 +18,12 @@ after_initialize do
DEFAULT_POLL_NAME ||= "poll".freeze
POLLS_CUSTOM_FIELD ||= "polls".freeze
VOTES_CUSTOM_FIELD ||= "polls-votes".freeze
MUTEX_PREFIX ||= PLUGIN_NAME
autoload :PostValidator, "#{Rails.root}/plugins/poll/lib/post_validator"
autoload :PollsValidator, "#{Rails.root}/plugins/poll/lib/polls_validator"
autoload :PollsUpdater, "#{Rails.root}/plugins/poll/lib/polls_updater"
autoload :VotesUpdater, "#{Rails.root}/plugins/poll/lib/votes_updater"
class Engine < ::Rails::Engine
engine_name PLUGIN_NAME
@ -385,6 +387,10 @@ after_initialize do
polls: post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD])
end
on(:merging_users) do |source_user, target_user|
DiscoursePoll::VotesUpdater.merge_users!(source_user, target_user)
end
add_to_serializer(:post, :polls, false) do
polls = post_custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD].dup

View File

@ -0,0 +1,94 @@
require 'rails_helper'
describe DiscoursePoll::VotesUpdater 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) }
let(:target_user_id) { target_user.id.to_s }
let(:source_user_id) { source_user.id.to_s }
let(:walter_id) { walter.id.to_s }
let(:post_with_two_polls) do
raw = <<~RAW
[poll type=multiple min=2 max=3 public=true]
- Option 1
- Option 2
- Option 3
[/poll]
[poll name=private_poll]
- Option 1
- Option 2
- Option 3
[/poll]
RAW
Fabricate(:post, raw: raw)
end
let(:option1_id) { "63eb791ab5d08fc4cc855a0703ac0dd1" }
let(:option2_id) { "773a193533027393806fff6edd6c04f7" }
let(:option3_id) { "f42f567ca3136ee1322d71d7745084c7" }
def vote(post, user, option_ids, poll_name = nil)
poll_name ||= DiscoursePoll::DEFAULT_POLL_NAME
DiscoursePoll::Poll.vote(post.id, poll_name, option_ids, user)
end
it "should move votes to the target_user when only the source_user voted" do
vote(post_with_two_polls, source_user, [option1_id, option3_id])
vote(post_with_two_polls, walter, [option1_id, option2_id])
DiscoursePoll::VotesUpdater.merge_users!(source_user, target_user)
post_with_two_polls.reload
polls = post_with_two_polls.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]
expect(polls["poll"]["options"][0]["votes"]).to eq(2)
expect(polls["poll"]["options"][1]["votes"]).to eq(1)
expect(polls["poll"]["options"][2]["votes"]).to eq(1)
expect(polls["poll"]["options"][0]["voter_ids"]).to contain_exactly(target_user.id, walter.id)
expect(polls["poll"]["options"][1]["voter_ids"]).to contain_exactly(walter.id)
expect(polls["poll"]["options"][2]["voter_ids"]).to contain_exactly(target_user.id)
votes = post_with_two_polls.custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD]
expect(votes.keys).to contain_exactly(target_user_id, walter_id)
expect(votes[target_user_id]["poll"]).to contain_exactly(option1_id, option3_id)
expect(votes[walter_id]["poll"]).to contain_exactly(option1_id, option2_id)
end
it "should delete votes of the source_user if the target_user voted" do
vote(post_with_two_polls, source_user, [option1_id, option3_id])
vote(post_with_two_polls, target_user, [option2_id, option3_id])
vote(post_with_two_polls, walter, [option1_id, option2_id])
DiscoursePoll::VotesUpdater.merge_users!(source_user, target_user)
post_with_two_polls.reload
polls = post_with_two_polls.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]
expect(polls["poll"]["options"][0]["votes"]).to eq(1)
expect(polls["poll"]["options"][1]["votes"]).to eq(2)
expect(polls["poll"]["options"][2]["votes"]).to eq(1)
expect(polls["poll"]["options"][0]["voter_ids"]).to contain_exactly(walter.id)
expect(polls["poll"]["options"][1]["voter_ids"]).to contain_exactly(target_user.id, walter.id)
expect(polls["poll"]["options"][2]["voter_ids"]).to contain_exactly(target_user.id)
votes = post_with_two_polls.custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD]
expect(votes.keys).to contain_exactly(target_user_id, walter_id)
expect(votes[target_user_id]["poll"]).to contain_exactly(option2_id, option3_id)
expect(votes[walter_id]["poll"]).to contain_exactly(option1_id, option2_id)
end
it "does not add voter_ids unless the poll is public" do
vote(post_with_two_polls, source_user, [option1_id, option3_id], "private_poll")
vote(post_with_two_polls, walter, [option1_id, option2_id], "private_poll")
DiscoursePoll::VotesUpdater.merge_users!(source_user, target_user)
post_with_two_polls.reload
polls = post_with_two_polls.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]
polls["private_poll"]["options"].each { |o| expect(o).to_not have_key("voter_ids") }
end
end

File diff suppressed because it is too large Load Diff