mirror of
https://github.com/discourse/discourse.git
synced 2024-11-27 19:06:20 +08:00
ac9577bcc7
This is causing issues when purging old users, if they are set up in the exact condition where they will be demoted into another group, but also do not have a primary email.
430 lines
14 KiB
Ruby
430 lines
14 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
describe UserDestroyer do
|
|
|
|
fab!(:user) { Fabricate(:user) }
|
|
fab!(:admin) { Fabricate(:admin) }
|
|
|
|
describe 'new' do
|
|
it 'raises an error when user is nil' do
|
|
expect { UserDestroyer.new(nil) }.to raise_error(Discourse::InvalidParameters)
|
|
end
|
|
|
|
it 'raises an error when user is not a User' do
|
|
expect { UserDestroyer.new(5) }.to raise_error(Discourse::InvalidParameters)
|
|
end
|
|
end
|
|
|
|
describe 'destroy' do
|
|
before do
|
|
@admin = Fabricate(:admin)
|
|
@user = Fabricate(:user_with_secondary_email)
|
|
end
|
|
|
|
it 'raises an error when user is nil' do
|
|
expect { UserDestroyer.new(@admin).destroy(nil) }.to raise_error(Discourse::InvalidParameters)
|
|
end
|
|
|
|
it 'raises an error when user is not a User' do
|
|
expect { UserDestroyer.new(@admin).destroy('nothing') }.to raise_error(Discourse::InvalidParameters)
|
|
end
|
|
|
|
it 'raises an error when regular user tries to delete another user' do
|
|
expect { UserDestroyer.new(@user).destroy(Fabricate(:user)) }.to raise_error(Discourse::InvalidAccess)
|
|
end
|
|
|
|
shared_examples "successfully destroy a user" do
|
|
it 'should delete the user' do
|
|
expect { destroy }.to change { User.count }.by(-1)
|
|
end
|
|
|
|
it 'should return the deleted user record' do
|
|
return_value = destroy
|
|
expect(return_value).to eq(@user)
|
|
expect(return_value).to be_destroyed
|
|
end
|
|
|
|
it 'should log the action' do
|
|
StaffActionLogger.any_instance.expects(:log_user_deletion).with(@user, anything).once
|
|
destroy
|
|
end
|
|
|
|
it "should not log the action if quiet is true" do
|
|
expect {
|
|
UserDestroyer.new(@admin).destroy(@user, destroy_opts.merge(quiet: true))
|
|
}.to_not change { UserHistory.where(action: UserHistory.actions[:delete_user]).count }
|
|
end
|
|
|
|
it 'triggers a extensibility event' do
|
|
event = DiscourseEvent.track_events { destroy }.last
|
|
|
|
expect(event[:event_name]).to eq(:user_destroyed)
|
|
expect(event[:params].first).to eq(@user)
|
|
end
|
|
end
|
|
|
|
shared_examples "email block list" do
|
|
it "doesn't add email to block list by default" do
|
|
ScreenedEmail.expects(:block).never
|
|
destroy
|
|
end
|
|
|
|
it "adds emails to block list if block_email is true" do
|
|
expect {
|
|
UserDestroyer.new(@admin).destroy(@user, destroy_opts.merge(block_email: true))
|
|
}.to change { ScreenedEmail.count }.by(2)
|
|
end
|
|
end
|
|
|
|
context 'user deletes self' do
|
|
let(:destroy_opts) { { delete_posts: true, context: "/u/username/preferences/account" } }
|
|
subject(:destroy) { UserDestroyer.new(@user).destroy(@user, destroy_opts) }
|
|
|
|
include_examples "successfully destroy a user"
|
|
|
|
it 'should log proper context' do
|
|
destroy
|
|
expect(UserHistory.where(action: UserHistory.actions[:delete_user]).last.context).to eq(I18n.t("staff_action_logs.user_delete_self", url: "/u/username/preferences/account"))
|
|
end
|
|
end
|
|
|
|
context "with a reviewable post" do
|
|
let!(:reviewable) { Fabricate(:reviewable, created_by: user) }
|
|
|
|
it "removes the queued post" do
|
|
UserDestroyer.new(admin).destroy(user)
|
|
expect(Reviewable.where(created_by_id: user.id).count).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "with a reviewable user" do
|
|
let(:reviewable) { Fabricate(:reviewable, created_by: admin) }
|
|
|
|
it 'sets the reviewable user as rejected' do
|
|
UserDestroyer.new(admin).destroy(reviewable.target)
|
|
|
|
expect(reviewable.reload.status).to eq(Reviewable.statuses[:rejected])
|
|
end
|
|
end
|
|
|
|
context "with a directory item record" do
|
|
|
|
it "removes the directory item" do
|
|
DirectoryItem.create!(
|
|
user: user,
|
|
period_type: 1,
|
|
likes_received: 0,
|
|
likes_given: 0,
|
|
topics_entered: 0,
|
|
topic_count: 0,
|
|
post_count: 0
|
|
)
|
|
UserDestroyer.new(admin).destroy(user)
|
|
expect(DirectoryItem.where(user_id: user.id).count).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "with a draft" do
|
|
let!(:draft) { Draft.set(user, 'test', 0, 'test') }
|
|
|
|
it "removed the draft" do
|
|
UserDestroyer.new(admin).destroy(user)
|
|
expect(Draft.where(user_id: user.id).count).to eq(0)
|
|
end
|
|
end
|
|
|
|
context 'user has posts' do
|
|
let!(:topic_starter) { Fabricate(:user) }
|
|
let!(:topic) { Fabricate(:topic, user: topic_starter) }
|
|
let!(:first_post) { Fabricate(:post, user: topic_starter, topic: topic) }
|
|
let!(:post) { Fabricate(:post, user: @user, topic: topic) }
|
|
|
|
context "delete_posts is false" do
|
|
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user) }
|
|
before do
|
|
@user.stubs(:post_count).returns(1)
|
|
@user.stubs(:first_post_created_at).returns(Time.zone.now)
|
|
end
|
|
|
|
it 'should raise the right error' do
|
|
StaffActionLogger.any_instance.expects(:log_user_deletion).never
|
|
expect { destroy }.to raise_error(UserDestroyer::PostsExistError)
|
|
expect(user.reload.id).to be_present
|
|
end
|
|
end
|
|
|
|
context "delete_posts is true" do
|
|
let(:destroy_opts) { { delete_posts: true } }
|
|
|
|
context "staff deletes user" do
|
|
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user, destroy_opts) }
|
|
|
|
include_examples "successfully destroy a user"
|
|
include_examples "email block list"
|
|
|
|
it "deletes the posts" do
|
|
destroy
|
|
expect(post.reload.deleted_at).not_to eq(nil)
|
|
expect(post.user_id).to eq(nil)
|
|
end
|
|
|
|
it "does not delete topics started by others in which the user has replies" do
|
|
destroy
|
|
expect(topic.reload.deleted_at).to eq(nil)
|
|
expect(topic.user_id).not_to eq(nil)
|
|
end
|
|
|
|
it "deletes topics started by the deleted user" do
|
|
spammer_topic = Fabricate(:topic, user: @user)
|
|
Fabricate(:post, user: @user, topic: spammer_topic)
|
|
destroy
|
|
expect(spammer_topic.reload.deleted_at).not_to eq(nil)
|
|
expect(spammer_topic.user_id).to eq(nil)
|
|
end
|
|
|
|
context "delete_as_spammer is true" do
|
|
|
|
before { destroy_opts[:delete_as_spammer] = true }
|
|
|
|
it "approves reviewable flags" do
|
|
spammer_post = Fabricate(:post, user: @user)
|
|
reviewable = PostActionCreator.inappropriate(@admin, spammer_post).reviewable
|
|
expect(reviewable).to be_pending
|
|
|
|
destroy
|
|
|
|
reviewable.reload
|
|
expect(reviewable).to be_approved
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
context "users deletes self" do
|
|
subject(:destroy) { UserDestroyer.new(@user).destroy(@user, destroy_opts) }
|
|
|
|
include_examples "successfully destroy a user"
|
|
include_examples "email block list"
|
|
|
|
it "deletes the posts" do
|
|
destroy
|
|
expect(post.reload.deleted_at).not_to eq(nil)
|
|
expect(post.user_id).to eq(nil)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'user has no posts, but user_stats table has post_count > 0' do
|
|
before do
|
|
# out of sync user_stat data shouldn't break UserDestroyer
|
|
@user.user_stat.update_attribute(:post_count, 1)
|
|
end
|
|
let(:destroy_opts) { {} }
|
|
subject(:destroy) { UserDestroyer.new(@user).destroy(@user, delete_posts: false) }
|
|
|
|
include_examples "successfully destroy a user"
|
|
end
|
|
|
|
context 'user has deleted posts' do
|
|
let!(:deleted_post) { Fabricate(:post, user: @user, deleted_at: 1.hour.ago) }
|
|
it "should mark the user's deleted posts as belonging to a nuked user" do
|
|
expect { UserDestroyer.new(@admin).destroy(@user) }.to change { User.count }.by(-1)
|
|
expect(deleted_post.reload.user_id).to eq(nil)
|
|
end
|
|
end
|
|
|
|
context 'user has no posts' do
|
|
context 'and destroy succeeds' do
|
|
let(:destroy_opts) { {} }
|
|
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user) }
|
|
|
|
include_examples "successfully destroy a user"
|
|
include_examples "email block list"
|
|
end
|
|
|
|
context 'and destroy fails' do
|
|
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user) }
|
|
|
|
it 'should not log the action' do
|
|
@user.stubs(:destroy).returns(false)
|
|
StaffActionLogger.any_instance.expects(:log_user_deletion).never
|
|
destroy
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'user has posts with links' do
|
|
context 'external links' do
|
|
before do
|
|
@post = Fabricate(:post_with_external_links, user: @user)
|
|
TopicLink.extract_from(@post)
|
|
end
|
|
|
|
it "doesn't add ScreenedUrl records by default" do
|
|
ScreenedUrl.expects(:watch).never
|
|
UserDestroyer.new(@admin).destroy(@user, delete_posts: true)
|
|
end
|
|
|
|
it "adds ScreenedUrl records when :block_urls is true" do
|
|
ScreenedUrl.expects(:watch).with(anything, anything, has_key(:ip_address)).at_least_once
|
|
UserDestroyer.new(@admin).destroy(@user, delete_posts: true, block_urls: true)
|
|
end
|
|
end
|
|
|
|
context 'internal links' do
|
|
before do
|
|
@post = Fabricate(:post_with_external_links, user: @user)
|
|
TopicLink.extract_from(@post)
|
|
TopicLink.any_instance.stubs(:internal).returns(true)
|
|
end
|
|
|
|
it "doesn't add ScreenedUrl records" do
|
|
ScreenedUrl.expects(:watch).never
|
|
UserDestroyer.new(@admin).destroy(@user, delete_posts: true, block_urls: true)
|
|
end
|
|
end
|
|
|
|
context 'with oneboxed links' do
|
|
before do
|
|
@post = Fabricate(:post_with_youtube, user: @user)
|
|
TopicLink.extract_from(@post)
|
|
end
|
|
|
|
it "doesn't add ScreenedUrl records" do
|
|
ScreenedUrl.expects(:watch).never
|
|
UserDestroyer.new(@admin).destroy(@user, delete_posts: true, block_urls: true)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'ip address screening' do
|
|
it "doesn't create screened_ip_address records by default" do
|
|
ScreenedIpAddress.expects(:watch).never
|
|
UserDestroyer.new(@admin).destroy(@user)
|
|
end
|
|
|
|
context "block_ip is true" do
|
|
it "creates a new screened_ip_address record" do
|
|
ScreenedIpAddress.expects(:watch).with(@user.ip_address).returns(stub_everything)
|
|
UserDestroyer.new(@admin).destroy(@user, block_ip: true)
|
|
end
|
|
|
|
it "creates two new screened_ip_address records when registration_ip_address is different than last ip_address" do
|
|
@user.registration_ip_address = '12.12.12.12'
|
|
ScreenedIpAddress.expects(:watch).with(@user.ip_address).returns(stub_everything)
|
|
ScreenedIpAddress.expects(:watch).with(@user.registration_ip_address).returns(stub_everything)
|
|
UserDestroyer.new(@admin).destroy(@user, block_ip: true)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'user created a category' do
|
|
let!(:category) { Fabricate(:category_with_definition, user: @user) }
|
|
|
|
it "assigns the system user to the categories" do
|
|
UserDestroyer.new(@admin).destroy(@user, delete_posts: true)
|
|
expect(category.reload.user_id).to eq(Discourse.system_user.id)
|
|
expect(category.topic).to be_present
|
|
expect(category.topic.user_id).to eq(Discourse.system_user.id)
|
|
end
|
|
end
|
|
|
|
describe "Destroying a user with security key" do
|
|
let!(:security_key) { Fabricate(:user_security_key_with_random_credential, user: user) }
|
|
fab!(:admin) { Fabricate(:admin) }
|
|
|
|
it "removes the security key" do
|
|
UserDestroyer.new(admin).destroy(user)
|
|
expect(UserSecurityKey.where(user_id: user.id).count).to eq(0)
|
|
end
|
|
end
|
|
|
|
describe "Destroying a user with a bookmark" do
|
|
let!(:bookmark) { Fabricate(:bookmark, user: user) }
|
|
fab!(:admin) { Fabricate(:admin) }
|
|
|
|
it "removes the bookmark" do
|
|
UserDestroyer.new(admin).destroy(user)
|
|
expect(Bookmark.where(user_id: user.id).count).to eq(0)
|
|
end
|
|
end
|
|
|
|
context 'user got an email' do
|
|
let!(:email_log) { Fabricate(:email_log, user: user) }
|
|
|
|
it "deletes the email log" do
|
|
expect {
|
|
UserDestroyer.new(@admin).destroy(user, delete_posts: true)
|
|
}.to change { EmailLog.count }.by(-1)
|
|
end
|
|
end
|
|
|
|
context 'user liked things' do
|
|
before do
|
|
@topic = Fabricate(:topic, user: Fabricate(:user))
|
|
@post = Fabricate(:post, user: @topic.user, topic: @topic)
|
|
PostActionCreator.like(@user, @post)
|
|
end
|
|
|
|
it 'should destroy the like' do
|
|
expect {
|
|
UserDestroyer.new(@admin).destroy(@user, delete_posts: true)
|
|
}.to change { PostAction.count }.by(-1)
|
|
expect(@post.reload.like_count).to eq(0)
|
|
end
|
|
end
|
|
|
|
context 'user belongs to groups that grant trust level' do
|
|
let(:group) { Fabricate(:group, grant_trust_level: 2) }
|
|
|
|
before do
|
|
group.add(user)
|
|
end
|
|
|
|
it 'can delete the user' do
|
|
d = UserDestroyer.new(admin)
|
|
expect {
|
|
d.destroy(user)
|
|
}.to change { User.count }.by(-1)
|
|
end
|
|
|
|
it 'can delete the user if they were to fall into another trust level and have no email' do
|
|
g2 = Fabricate(:group, grant_trust_level: 1)
|
|
g2.add(user)
|
|
|
|
UserEmail.where(user: user).delete_all
|
|
user.reload
|
|
expect {
|
|
UserDestroyer.new(admin).destroy(user)
|
|
}.to change { User.count }.by(-1)
|
|
end
|
|
end
|
|
|
|
context 'user has staff action logs' do
|
|
before do
|
|
logger = StaffActionLogger.new(user)
|
|
logger.log_site_setting_change(
|
|
'site_description',
|
|
'Our friendly community',
|
|
'My favourite community'
|
|
)
|
|
end
|
|
|
|
it "should keep the staff action log and add the username" do
|
|
username = user.username
|
|
log = UserHistory.staff_action_records(
|
|
Discourse.system_user,
|
|
acting_user: username
|
|
).to_a[0]
|
|
UserDestroyer.new(admin).destroy(user, delete_posts: true)
|
|
log.reload
|
|
expect(log.details).to include(username)
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|