require 'rails_helper' require 'post_destroyer' describe PostDestroyer do before do ActiveRecord::Base.observers.enable :all end let(:moderator) { Fabricate(:moderator) } let(:admin) { Fabricate(:admin) } let(:post) { create_post } describe "destroy_old_hidden_posts" do it "destroys posts that have been hidden for 30 days" do Fabricate(:admin) now = Time.now freeze_time(now - 60.days) topic = post.topic reply1 = create_post(topic: topic) freeze_time(now - 40.days) reply2 = create_post(topic: topic) PostAction.hide_post!(reply2, PostActionType.types[:off_topic]) freeze_time(now - 20.days) reply3 = create_post(topic: topic) PostAction.hide_post!(reply3, PostActionType.types[:off_topic]) freeze_time(now - 10.days) reply4 = create_post(topic: topic) freeze_time(now) PostDestroyer.destroy_old_hidden_posts reply1.reload reply2.reload reply3.reload reply4.reload expect(reply1.deleted_at).to eq(nil) expect(reply2.deleted_at).not_to eq(nil) expect(reply3.deleted_at).to eq(nil) expect(reply4.deleted_at).to eq(nil) end end describe 'destroy_old_stubs' do it 'destroys stubs for deleted by user posts' do SiteSetting.stubs(:delete_removed_posts_after).returns(24) Fabricate(:admin) topic = post.topic reply1 = create_post(topic: topic) reply2 = create_post(topic: topic) reply3 = create_post(topic: topic) PostDestroyer.new(reply1.user, reply1).destroy PostDestroyer.new(reply2.user, reply2).destroy reply2.update_column(:updated_at, 2.days.ago) PostDestroyer.destroy_stubs reply1.reload reply2.reload reply3.reload expect(reply1.deleted_at).to eq(nil) expect(reply2.deleted_at).not_to eq(nil) expect(reply3.deleted_at).to eq(nil) # if topic is deleted we should still be able to destroy stubs topic.trash! reply1.update_column(:updated_at, 2.days.ago) PostDestroyer.destroy_stubs reply1.reload expect(reply1.deleted_at).to eq(nil) # flag the post, it should not nuke the stub anymore topic.recover! PostAction.act(Fabricate(:coding_horror), reply1, PostActionType.types[:spam]) PostDestroyer.destroy_stubs reply1.reload expect(reply1.deleted_at).to eq(nil) end it 'uses the delete_removed_posts_after site setting' do Fabricate(:admin) topic = post.topic reply1 = create_post(topic: topic) reply2 = create_post(topic: topic) PostDestroyer.new(reply1.user, reply1).destroy PostDestroyer.new(reply2.user, reply2).destroy SiteSetting.stubs(:delete_removed_posts_after).returns(1) reply2.update_column(:updated_at, 70.minutes.ago) PostDestroyer.destroy_stubs reply1.reload reply2.reload expect(reply1.deleted_at).to eq(nil) expect(reply2.deleted_at).not_to eq(nil) SiteSetting.stubs(:delete_removed_posts_after).returns(72) reply1.update_column(:updated_at, 2.days.ago) PostDestroyer.destroy_stubs expect(reply1.reload.deleted_at).to eq(nil) SiteSetting.stubs(:delete_removed_posts_after).returns(47) PostDestroyer.destroy_stubs expect(reply1.reload.deleted_at).not_to eq(nil) end it "deletes posts immediately if delete_removed_posts_after is 0" do Fabricate(:admin) topic = post.topic reply1 = create_post(topic: topic) SiteSetting.stubs(:delete_removed_posts_after).returns(0) PostDestroyer.new(reply1.user, reply1).destroy expect(reply1.reload.deleted_at).not_to eq(nil) end end describe "recovery and user actions" do it "recreates user actions" do reply = create_post(topic: post.topic) author = reply.user post_action = author.user_actions.where(action_type: UserAction::REPLY, target_post_id: reply.id).first expect(post_action).to be_present PostDestroyer.new(moderator, reply).destroy # User Action is removed post_action = author.user_actions.where(action_type: UserAction::REPLY, target_post_id: reply.id).first expect(post_action).to be_blank PostDestroyer.new(moderator, reply).recover # On recovery, the user action is recreated post_action = author.user_actions.where(action_type: UserAction::REPLY, target_post_id: reply.id).first expect(post_action).to be_present end describe "post_count recovery" do before do post @user = post.user expect(@user.user_stat.post_count).to eq(1) end context "recovered by user" do it "should increment the user's post count" do PostDestroyer.new(@user, post).destroy expect(@user.user_stat.post_count).to eq(1) PostDestroyer.new(@user, post.reload).recover expect(@user.reload.user_stat.post_count).to eq(1) end end context "recovered by admin" do it "should increment the user's post count" do PostDestroyer.new(moderator, post).destroy expect(@user.user_stat.post_count).to eq(0) PostDestroyer.new(admin, post).recover expect(@user.reload.user_stat.post_count).to eq(1) end end end end describe 'basic destroying' do it "as the creator of the post, doesn't delete the post" do begin post2 = create_post called = 0 topic_destroyed = -> (topic, user) do expect(topic).to eq(post2.topic) expect(user).to eq(post2.user) called += 1 end DiscourseEvent.on(:topic_destroyed, &topic_destroyed) @orig = post2.cooked PostDestroyer.new(post2.user, post2).destroy post2.reload expect(post2.deleted_at).to be_blank expect(post2.deleted_by).to be_blank expect(post2.user_deleted).to eq(true) expect(post2.raw).to eq(I18n.t('js.post.deleted_by_author', {count: 24})) expect(post2.version).to eq(2) expect(called).to eq(1) called = 0 topic_recovered = -> (topic, user) do expect(topic).to eq(post2.topic) expect(user).to eq(post2.user) called += 1 end DiscourseEvent.on(:topic_recovered, &topic_recovered) # lets try to recover PostDestroyer.new(post2.user, post2).recover post2.reload expect(post2.version).to eq(3) expect(post2.user_deleted).to eq(false) expect(post2.cooked).to eq(@orig) expect(called).to eq(1) ensure DiscourseEvent.off(:topic_destroyed, &topic_destroyed) DiscourseEvent.off(:topic_recovered, &topic_recovered) end end context "as a moderator" do it "deletes the post" do author = post.user post_count = author.post_count history_count = UserHistory.count PostDestroyer.new(moderator, post).destroy expect(post.deleted_at).to be_present expect(post.deleted_by).to eq(moderator) author.reload expect(author.post_count).to eq(post_count - 1) expect(UserHistory.count).to eq(history_count + 1) end end context "as an admin" do it "deletes the post" do PostDestroyer.new(admin, post).destroy expect(post.deleted_at).to be_present expect(post.deleted_by).to eq(admin) end it "updates the user's post_count" do author = post.user expect { PostDestroyer.new(admin, post).destroy author.reload }.to change { author.post_count }.by(-1) end end end context 'deleting the second post in a topic' do let(:user) { Fabricate(:user) } let!(:post) { create_post(user: user) } let(:topic) { post.topic.reload } let(:second_user) { Fabricate(:coding_horror) } let!(:second_post) { create_post(topic: topic, user: second_user) } before do PostDestroyer.new(moderator, second_post).destroy end it 'resets the last_poster_id back to the OP' do expect(topic.last_post_user_id).to eq(user.id) end it 'resets the last_posted_at back to the OP' do expect(topic.last_posted_at.to_i).to eq(post.created_at.to_i) end context 'topic_user' do let(:topic_user) { second_user.topic_users.find_by(topic_id: topic.id) } it 'clears the posted flag for the second user' do expect(topic_user.posted?).to eq(false) end it "sets the second user's last_read_post_number back to 1" do expect(topic_user.last_read_post_number).to eq(1) end it "sets the second user's last_read_post_number back to 1" do expect(topic_user.highest_seen_post_number).to eq(1) end end end context "deleting a post belonging to a deleted topic" do let!(:topic) { post.topic } before do topic.trash!(admin) post.reload end context "as a moderator" do before do PostDestroyer.new(moderator, post).destroy end it "deletes the post" do expect(post.deleted_at).to be_present expect(post.deleted_by).to eq(moderator) end end context "as an admin" do before do PostDestroyer.new(admin, post).destroy end it "deletes the post" do expect(post.deleted_at).to be_present expect(post.deleted_by).to eq(admin) end it "creates a new user history entry" do expect { PostDestroyer.new(admin, post).destroy }.to change { UserHistory.count}.by(1) end end end describe 'after delete' do let!(:coding_horror) { Fabricate(:coding_horror) } let!(:post) { Fabricate(:post, raw: "Hello @CodingHorror") } it "should feature the users again (in case they've changed)" do Jobs.expects(:enqueue).with(:feature_topic_users, has_entries(topic_id: post.topic_id)) PostDestroyer.new(moderator, post).destroy end describe 'with a reply' do let!(:reply) { Fabricate(:basic_reply, user: coding_horror, topic: post.topic) } let!(:post_reply) { PostReply.create(post_id: post.id, reply_id: reply.id) } it 'changes the post count of the topic' do post.reload expect { PostDestroyer.new(moderator, reply).destroy post.topic.reload }.to change(post.topic, :posts_count).by(-1) end it 'lowers the reply_count when the reply is deleted' do expect { PostDestroyer.new(moderator, reply).destroy }.to change(post.post_replies, :count).by(-1) end it 'should increase the post_number when there are deletion gaps' do PostDestroyer.new(moderator, reply).destroy p = Fabricate(:post, user: post.user, topic: post.topic) expect(p.post_number).to eq(3) end end end context '@mentions' do it 'removes notifications when deleted' do user = Fabricate(:evil_trout) post = create_post(raw: 'Hello @eviltrout') expect { PostDestroyer.new(Fabricate(:moderator), post).destroy }.to change(user.notifications, :count).by(-1) end end describe "post actions" do let(:second_post) { Fabricate(:post, topic_id: post.topic_id) } let!(:bookmark) { PostAction.act(moderator, second_post, PostActionType.types[:bookmark]) } let!(:flag) { PostAction.act(moderator, second_post, PostActionType.types[:off_topic]) } it "should delete public post actions and agree with flags" do second_post.expects(:update_flagged_posts_count) PostDestroyer.new(moderator, second_post).destroy expect(PostAction.find_by(id: bookmark.id)).to eq(nil) off_topic = PostAction.find_by(id: flag.id) expect(off_topic).not_to eq(nil) expect(off_topic.agreed_at).not_to eq(nil) second_post.reload expect(second_post.bookmark_count).to eq(0) expect(second_post.off_topic_count).to eq(1) end end describe "user actions" do let(:codinghorror) { Fabricate(:coding_horror) } let(:second_post) { Fabricate(:post, topic_id: post.topic_id) } def create_user_action(action_type) UserAction.log_action!({ action_type: action_type, user_id: codinghorror.id, acting_user_id: codinghorror.id, target_topic_id: second_post.topic_id, target_post_id: second_post.id }) end it "should delete the user actions" do bookmark = create_user_action(UserAction::BOOKMARK) like = create_user_action(UserAction::LIKE) PostDestroyer.new(moderator, second_post).destroy expect(UserAction.find_by(id: bookmark.id)).to be_nil expect(UserAction.find_by(id: like.id)).to be_nil end end describe 'topic links' do let!(:first_post) { Fabricate(:post) } let!(:topic) { first_post.topic } let!(:second_post) { Fabricate(:post_with_external_links, topic: topic) } before { TopicLink.extract_from(second_post) } it 'should destroy the topic links when moderator destroys the post' do PostDestroyer.new(moderator, second_post.reload).destroy expect(topic.topic_links.count).to eq(0) end it 'should destroy the topic links when the user destroys the post' do PostDestroyer.new(second_post.user, second_post.reload).destroy expect(topic.topic_links.count).to eq(0) end end end