require 'rails_helper' require_dependency 'post_destroyer' describe PostAction do it { is_expected.to rate_limit } let(:moderator) { Fabricate(:moderator) } let(:codinghorror) { Fabricate(:coding_horror) } let(:eviltrout) { Fabricate(:evil_trout) } let(:admin) { Fabricate(:admin) } let(:post) { Fabricate(:post) } let(:second_post) { Fabricate(:post, topic: post.topic) } let(:bookmark) { PostAction.new(user_id: post.user_id, post_action_type_id: PostActionType.types[:bookmark] , post_id: post.id) } def value_for(user_id, dt) GivenDailyLike.find_for(user_id, dt).pluck(:likes_given)[0] || 0 end describe "rate limits" do it "limits redo/undo" do RateLimiter.enable PostAction.act(eviltrout, post, PostActionType.types[:like]) PostAction.remove_act(eviltrout, post, PostActionType.types[:like]) PostAction.act(eviltrout, post, PostActionType.types[:like]) PostAction.remove_act(eviltrout, post, PostActionType.types[:like]) expect { PostAction.act(eviltrout, post, PostActionType.types[:like]) }.to raise_error(RateLimiter::LimitExceeded) end end describe "messaging" do it "doesn't generate title longer than 255 characters" do topic = create_topic(title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc sit amet rutrum neque. Pellentesque suscipit vehicula facilisis. Phasellus lacus sapien, aliquam nec convallis sit amet, vestibulum laoreet ante. Curabitur et pellentesque tortor. Donec non.") post = create_post(topic: topic) expect { PostAction.act(admin, post, PostActionType.types[:notify_user], message: "WAT") }.not_to raise_error end it "notify moderators integration test" do post = create_post mod = moderator Group.refresh_automatic_groups! action = PostAction.act(codinghorror, post, PostActionType.types[:notify_moderators], message: "this is my special long message") posts = Post.joins(:topic) .select('posts.id, topics.subtype, posts.topic_id') .where('topics.archetype' => Archetype.private_message) .to_a expect(posts.count).to eq(1) expect(action.related_post_id).to eq(posts[0].id.to_i) expect(posts[0].subtype).to eq(TopicSubtype.notify_moderators) topic = posts[0].topic # Moderators should be invited to the private topic, otherwise they're not permitted to see it topic_user_ids = topic.reload.topic_users.map { |x| x.user_id } expect(topic_user_ids).to include(codinghorror.id) expect(topic_user_ids).to include(mod.id) expect(topic.topic_users.where(user_id: mod.id) .pluck(:notification_level).first).to eq(TopicUser.notification_levels[:tracking]) expect(topic.topic_users.where(user_id: codinghorror.id) .pluck(:notification_level).first).to eq(TopicUser.notification_levels[:watching]) # reply to PM should not clear flag PostCreator.new(mod, topic_id: posts[0].topic_id, raw: "This is my test reply to the user, it should clear flags").create action.reload expect(action.deleted_at).to eq(nil) # Acting on the flag should not post an automated status message (since a moderator already replied) expect(topic.posts.count).to eq(2) PostAction.agree_flags!(post, admin) expect(action.user.user_stat.flags_agreed).to eq(1) expect(action.user.user_stat.flags_disagreed).to eq(0) topic.reload expect(topic.posts.count).to eq(2) # Clearing the flags should not post an automated status message new_action = PostAction.act(mod, post, PostActionType.types[:notify_moderators], message: "another special message") PostAction.clear_flags!(post, admin) expect(new_action.user.user_stat.flags_agreed).to eq(0) expect(new_action.user.user_stat.flags_disagreed).to eq(1) topic.reload expect(topic.posts.count).to eq(2) # Acting on the flag should post an automated status message another_post = create_post action = PostAction.act(codinghorror, another_post, PostActionType.types[:notify_moderators], message: "foobar") topic = action.related_post.topic expect(topic.posts.count).to eq(1) PostAction.agree_flags!(another_post, admin) expect(action.user.user_stat.flags_agreed).to eq(2) expect(action.user.user_stat.flags_disagreed).to eq(0) topic.reload expect(topic.posts.count).to eq(2) expect(topic.posts.last.post_type).to eq(Post.types[:moderator_action]) end describe 'notify_moderators' do before do PostAction.stubs(:create) end it "creates a pm if selected" do post = build(:post, id: 1000) PostCreator.any_instance.expects(:create).returns(post) PostAction.act(build(:user), build(:post), PostActionType.types[:notify_moderators], message: "this is my special message") end end describe "notify_user" do before do PostAction.stubs(:create) post = build(:post) post.user = build(:user) end it "sends an email to user if selected" do PostCreator.any_instance.expects(:create).returns(build(:post)) PostAction.act(build(:user), post, PostActionType.types[:notify_user], message: "this is my special message") end end end describe "flag counts" do before do PostAction.update_flagged_posts_count end it "increments the numbers correctly" do expect(PostAction.flagged_posts_count).to eq(0) PostAction.act(codinghorror, post, PostActionType.types[:off_topic]) expect(PostAction.flagged_posts_count).to eq(1) PostAction.clear_flags!(post, Discourse.system_user) expect(PostAction.flagged_posts_count).to eq(0) end it "respects min_flags_staff_visibility" do SiteSetting.min_flags_staff_visibility = 2 expect(PostAction.flagged_posts_count).to eq(0) PostAction.act(codinghorror, post, PostActionType.types[:off_topic]) expect(PostAction.flagged_posts_count).to eq(0) PostAction.act(eviltrout, post, PostActionType.types[:off_topic]) expect(PostAction.flagged_posts_count).to eq(1) end it "tl3 hidden posts will supersede min_flags_staff_visibility" do SiteSetting.min_flags_staff_visibility = 2 expect(PostAction.flagged_posts_count).to eq(0) codinghorror.update_column(:trust_level, 3) post.user.update_column(:trust_level, 0) PostAction.act(codinghorror, post, PostActionType.types[:spam]) expect(PostAction.flagged_posts_count).to eq(1) end it "tl4 hidden posts will supersede min_flags_staff_visibility" do SiteSetting.min_flags_staff_visibility = 2 expect(PostAction.flagged_posts_count).to eq(0) codinghorror.update_column(:trust_level, 4) PostAction.act(codinghorror, post, PostActionType.types[:off_topic]) expect(PostAction.flagged_posts_count).to eq(1) end it "should reset counts when a topic is deleted" do PostAction.act(codinghorror, post, PostActionType.types[:off_topic]) post.topic.trash! expect(PostAction.flagged_posts_count).to eq(0) end it "should ignore flags on non-human users" do post = create_post(user: Discourse.system_user) PostAction.act(codinghorror, post, PostActionType.types[:off_topic]) expect(PostAction.flagged_posts_count).to eq(0) end it "should ignore validated flags" do post = create_post PostAction.act(codinghorror, post, PostActionType.types[:off_topic]) expect(post.hidden).to eq(false) expect(post.hidden_at).to be_blank PostAction.defer_flags!(post, admin) expect(PostAction.flagged_posts_count).to eq(0) post.reload expect(post.hidden).to eq(false) expect(post.hidden_at).to be_blank PostAction.hide_post!(post, PostActionType.types[:off_topic]) post.reload expect(post.hidden).to eq(true) expect(post.hidden_at).to be_present end end describe "update_counters" do it "properly updates topic counters" do freeze_time Date.today # we need this to test it TopicUser.change(codinghorror, post.topic, posted: true) expect(value_for(moderator.id, Date.today)).to eq(0) PostAction.act(moderator, post, PostActionType.types[:like]) PostAction.act(codinghorror, second_post, PostActionType.types[:like]) post.topic.reload expect(post.topic.like_count).to eq(2) expect(value_for(moderator.id, Date.today)).to eq(1) tu = TopicUser.get(post.topic, codinghorror) expect(tu.liked).to be true expect(tu.bookmarked).to be false end end describe "when a user bookmarks something" do it "increases the post's bookmark count when saved" do expect { bookmark.save; post.reload }.to change(post, :bookmark_count).by(1) end describe 'when deleted' do before do bookmark.save post.reload @topic = post.topic @topic.reload bookmark.deleted_at = DateTime.now bookmark.save end it 'reduces the bookmark count of the post' do expect { post.reload }.to change(post, :bookmark_count).by(-1) end end end describe "undo/redo repeatedly" do it "doesn't create a second action for the same user/type" do PostAction.act(codinghorror, post, PostActionType.types[:like]) PostAction.remove_act(codinghorror, post, PostActionType.types[:like]) PostAction.act(codinghorror, post, PostActionType.types[:like]) expect(PostAction.where(post: post).with_deleted.count).to eq(1) PostAction.remove_act(codinghorror, post, PostActionType.types[:like]) # Check that we don't lose consistency into negatives expect(post.reload.like_count).to eq(0) end end describe 'when a user likes something' do before do PostActionNotifier.enable end it 'should generate and remove notifications correctly' do PostAction.act(codinghorror, post, PostActionType.types[:like]) expect(Notification.count).to eq(1) notification = Notification.last expect(notification.user_id).to eq(post.user_id) expect(notification.notification_type).to eq(Notification.types[:liked]) PostAction.remove_act(codinghorror, post, PostActionType.types[:like]) expect(Notification.count).to eq(0) PostAction.act(codinghorror, post, PostActionType.types[:like]) expect(Notification.count).to eq(1) notification = Notification.last expect(notification.user_id).to eq(post.user_id) expect(notification.notification_type).to eq(Notification.types[:liked]) end it 'should not notify when never is selected' do post.user.user_option.update!( like_notification_frequency: UserOption.like_notification_frequency_type[:never] ) expect do PostAction.act(codinghorror, post, PostActionType.types[:like]) end.to_not change { Notification.count } end it 'notifies on likes correctly' do PostAction.act(eviltrout, post, PostActionType.types[:like]) PostAction.act(admin, post, PostActionType.types[:like]) # one like expect(Notification.where(post_number: 1, topic_id: post.topic_id).count) .to eq(1) post.user.user_option.update!( like_notification_frequency: UserOption.like_notification_frequency_type[:always] ) admin2 = Fabricate(:admin) # Travel 1 hour in time to test that order post_actions by `created_at` freeze_time 1.hour.from_now expect do PostAction.act(admin2, post, PostActionType.types[:like]) end.to_not change { Notification.count } # adds info to the notification notification = Notification.find_by( post_number: 1, topic_id: post.topic_id ) expect(notification.data_hash["count"].to_i).to eq(2) expect(notification.data_hash["username2"]).to eq(eviltrout.username) # this is a tricky thing ... removing a like should fix up the notifications PostAction.remove_act(eviltrout, post, PostActionType.types[:like]) # rebuilds the missing notification expect(Notification.where(post_number: 1, topic_id: post.topic_id).count) .to eq(1) notification = Notification.find_by( post_number: 1, topic_id: post.topic_id ) expect(notification.data_hash["count"]).to eq(2) expect(notification.data_hash["username"]).to eq(admin2.username) expect(notification.data_hash["username2"]).to eq(admin.username) post.user.user_option.update!( like_notification_frequency: UserOption.like_notification_frequency_type[:first_time_and_daily] ) # this gets skipped admin3 = Fabricate(:admin) PostAction.act(admin3, post, PostActionType.types[:like]) freeze_time 2.days.from_now admin4 = Fabricate(:admin) PostAction.act(admin4, post, PostActionType.types[:like]) # first happend within the same day, no need to notify expect(Notification.where(post_number: 1, topic_id: post.topic_id).count) .to eq(2) end describe 'likes consolidation' do let(:liker) { Fabricate(:user) } let(:liker2) { Fabricate(:user) } let(:likee) { Fabricate(:user) } it "can be disabled" do SiteSetting.likes_notification_consolidation_threshold = 0 expect do PostAction.act( liker, Fabricate(:post, user: likee), PostActionType.types[:like] ) end.to change { likee.reload.notifications.count }.by(1) SiteSetting.likes_notification_consolidation_threshold = 1 expect do PostAction.act( liker, Fabricate(:post, user: likee), PostActionType.types[:like] ) end.to_not change { likee.reload.notifications.count } end describe 'frequency first_time_and_daily' do before do likee.user_option.update!( like_notification_frequency: UserOption.like_notification_frequency_type[:first_time_and_daily] ) end it 'should consolidate likes notification when the threshold is reached' do SiteSetting.likes_notification_consolidation_threshold = 2 expect do 3.times do PostAction.act( liker, Fabricate(:post, user: likee), PostActionType.types[:like] ) end end.to change { likee.reload.notifications.count }.by(1) notification = likee.notifications.last expect(notification.notification_type).to eq( Notification.types[:liked_consolidated] ) data = JSON.parse(notification.data) expect(data["username"]).to eq(liker.username) expect(data["display_username"]).to eq(liker.username) expect(data["count"]).to eq(3) notification.update!(read: true) expect do 2.times do PostAction.act( liker, Fabricate(:post, user: likee), PostActionType.types[:like] ) end end.to_not change { likee.reload.notifications.count } data = JSON.parse(notification.reload.data) expect(notification.read).to eq(false) expect(data["count"]).to eq(5) # Like from a different user shouldn't be consolidated expect do PostAction.act( Fabricate(:user), Fabricate(:post, user: likee), PostActionType.types[:like] ) end.to change { likee.reload.notifications.count }.by(1) notification = likee.notifications.last expect(notification.notification_type).to eq( Notification.types[:liked] ) freeze_time(( SiteSetting.likes_notification_consolidation_window_mins.minutes + 1 ).since) expect do PostAction.act( liker, Fabricate(:post, user: likee), PostActionType.types[:like] ) end.to change { likee.reload.notifications.count }.by(1) notification = likee.notifications.last expect(notification.notification_type).to eq(Notification.types[:liked]) end end describe 'frequency always' do before do likee.user_option.update!( like_notification_frequency: UserOption.like_notification_frequency_type[:always] ) end it 'should consolidate liked notifications when threshold is reached' do SiteSetting.likes_notification_consolidation_threshold = 2 post = Fabricate(:post, user: likee) expect do [liker2, liker].each do |user| PostAction.act(user, post, PostActionType.types[:like]) end end.to change { likee.reload.notifications.count }.by(1) notification = likee.notifications.last data_hash = notification.data_hash expect(data_hash["original_username"]).to eq(liker.username) expect(data_hash["username2"]).to eq(liker2.username) expect(data_hash["count"].to_i).to eq(2) expect do 2.times do PostAction.act( liker, Fabricate(:post, user: likee), PostActionType.types[:like] ) end end.to change { likee.reload.notifications.count }.by(2) expect(likee.notifications.pluck(:notification_type).uniq) .to contain_exactly(Notification.types[:liked]) expect do PostAction.act( liker, Fabricate(:post, user: likee), PostActionType.types[:like] ) end.to change { likee.reload.notifications.count }.by(-1) notification = likee.notifications.last expect(notification.notification_type).to eq( Notification.types[:liked_consolidated] ) expect(notification.data_hash["count"].to_i).to eq(3) expect(notification.data_hash["username"]).to eq(liker.username) end end end it "should not generate a notification if liker has been muted" do mutee = Fabricate(:user) MutedUser.create!(user_id: post.user.id, muted_user_id: mutee.id) expect do PostAction.act(mutee, post, PostActionType.types[:like]) end.to_not change { Notification.count } end it 'should not generate a notification if liker has the topic muted' do post = Fabricate(:post, user: eviltrout) TopicUser.create!( topic: post.topic, user: eviltrout, notification_level: TopicUser.notification_levels[:muted] ) expect do PostAction.act(codinghorror, post, PostActionType.types[:like]) end.to_not change { Notification.count } end it "should generate a notification if liker is an admin irregardles of \ muting" do MutedUser.create!(user_id: post.user.id, muted_user_id: admin.id) expect do PostAction.act(admin, post, PostActionType.types[:like]) end.to change { Notification.count }.by(1) notification = Notification.last expect(notification.user_id).to eq(post.user_id) expect(notification.notification_type).to eq(Notification.types[:liked]) end it 'should increase the `like_count` and `like_score` when a user likes something' do freeze_time Date.today PostAction.act(codinghorror, post, PostActionType.types[:like]) post.reload expect(post.like_count).to eq(1) expect(post.like_score).to eq(1) post.topic.reload expect(post.topic.like_count).to eq(1) expect(value_for(codinghorror.id, Date.today)).to eq(1) # When a staff member likes it PostAction.act(moderator, post, PostActionType.types[:like]) post.reload expect(post.like_count).to eq(2) expect(post.like_score).to eq(4) expect(post.topic.like_count).to eq(2) # Removing likes PostAction.remove_act(codinghorror, post, PostActionType.types[:like]) post.reload expect(post.like_count).to eq(1) expect(post.like_score).to eq(3) expect(post.topic.like_count).to eq(1) expect(value_for(codinghorror.id, Date.today)).to eq(0) PostAction.remove_act(moderator, post, PostActionType.types[:like]) post.reload expect(post.like_count).to eq(0) expect(post.like_score).to eq(0) expect(post.topic.like_count).to eq(0) end it "shouldn't change given_likes unless likes are given or removed" do freeze_time(Time.zone.now) PostAction.act(codinghorror, Fabricate(:post), PostActionType.types[:like]) expect(value_for(codinghorror.id, Date.today)).to eq(1) PostActionType.types.each do |type_name, type_id| post = Fabricate(:post) PostAction.act(codinghorror, post, type_id) actual_count = value_for(codinghorror.id, Date.today) expected_count = type_name == :like ? 2 : 1 expect(actual_count).to eq(expected_count), "Expected likes_given to be #{expected_count} when adding '#{type_name}', but got #{actual_count}" PostAction.remove_act(codinghorror, post, type_id) actual_count = value_for(codinghorror.id, Date.today) expect(actual_count).to eq(1), "Expected likes_given to be 1 when removing '#{type_name}', but got #{actual_count}" end end end describe 'flagging' do context "flag_counts_for" do it "returns the correct flag counts" do post = create_post SiteSetting.flags_required_to_hide_post = 7 # A post with no flags has 0 for flag counts expect(PostAction.flag_counts_for(post.id)).to eq([0, 0]) _flag = PostAction.act(eviltrout, post, PostActionType.types[:spam]) expect(PostAction.flag_counts_for(post.id)).to eq([0, 1]) # If staff takes action, it is ranked higher PostAction.act(admin, post, PostActionType.types[:spam], take_action: true) expect(PostAction.flag_counts_for(post.id)).to eq([0, 8]) # If a flag is dismissed PostAction.clear_flags!(post, admin) expect(PostAction.flag_counts_for(post.id)).to eq([0, 8]) end end it 'does not allow you to flag stuff with the same reason more than once' do post = Fabricate(:post) PostAction.act(eviltrout, post, PostActionType.types[:spam]) expect { PostAction.act(eviltrout, post, PostActionType.types[:off_topic]) }.to raise_error(PostAction::AlreadyActed) end it 'allows you to flag stuff with another reason' do post = Fabricate(:post) PostAction.act(eviltrout, post, PostActionType.types[:spam]) PostAction.remove_act(eviltrout, post, PostActionType.types[:spam]) expect { PostAction.act(eviltrout, post, PostActionType.types[:off_topic]) }.not_to raise_error() end it 'should update counts when you clear flags' do post = Fabricate(:post) PostAction.act(eviltrout, post, PostActionType.types[:spam]) post.reload expect(post.spam_count).to eq(1) PostAction.clear_flags!(post, Discourse.system_user) post.reload expect(post.spam_count).to eq(0) end it "will not allow regular users to auto hide staff posts" do mod = Fabricate(:moderator) post = Fabricate(:post, user: mod) SiteSetting.flags_required_to_hide_post = 2 Discourse.stubs(:site_contact_user).returns(admin) PostAction.act(eviltrout, post, PostActionType.types[:spam]) PostAction.act(Fabricate(:walter_white), post, PostActionType.types[:spam]) post.reload expect(post.hidden).to eq(false) expect(post.hidden_at).to be_blank end it "allows staff users to auto hide staff posts" do mod = Fabricate(:moderator) post = Fabricate(:post, user: mod) SiteSetting.flags_required_to_hide_post = 2 Discourse.stubs(:site_contact_user).returns(admin) PostAction.act(eviltrout, post, PostActionType.types[:spam]) PostAction.act(Fabricate(:admin), post, PostActionType.types[:spam]) post.reload expect(post.hidden).to eq(true) expect(post.hidden_at).to be_present end it 'should follow the rules for automatic hiding workflow' do post = create_post walterwhite = Fabricate(:walter_white) SiteSetting.flags_required_to_hide_post = 2 Discourse.stubs(:site_contact_user).returns(admin) PostAction.act(eviltrout, post, PostActionType.types[:spam]) PostAction.act(walterwhite, post, PostActionType.types[:spam]) job_args = Jobs::SendSystemMessage.jobs.last["args"].first expect(job_args["user_id"]).to eq(post.user.id) expect(job_args["message_type"]).to eq("post_hidden") post.reload expect(post.hidden).to eq(true) expect(post.hidden_at).to be_present expect(post.hidden_reason_id).to eq(Post.hidden_reasons[:flag_threshold_reached]) expect(post.topic.visible).to eq(false) post.revise(post.user, raw: post.raw + " ha I edited it ") post.reload expect(post.hidden).to eq(false) expect(post.hidden_reason_id).to eq(Post.hidden_reasons[:flag_threshold_reached]) # keep most recent reason expect(post.hidden_at).to be_present # keep the most recent hidden_at time expect(post.topic.visible).to eq(true) PostAction.act(eviltrout, post, PostActionType.types[:spam]) PostAction.act(walterwhite, post, PostActionType.types[:off_topic]) job_args = Jobs::SendSystemMessage.jobs.last["args"].first expect(job_args["user_id"]).to eq(post.user.id) expect(job_args["message_type"]).to eq("post_hidden_again") post.reload expect(post.hidden).to eq(true) expect(post.hidden_at).to be_present expect(post.hidden_reason_id).to eq(Post.hidden_reasons[:flag_threshold_reached_again]) expect(post.topic.visible).to eq(false) post.revise(post.user, raw: post.raw + " ha I edited it again ") post.reload expect(post.hidden).to eq(true) expect(post.hidden_at).to be_present expect(post.hidden_reason_id).to eq(Post.hidden_reasons[:flag_threshold_reached_again]) expect(post.topic.visible).to eq(false) end it "doesn't fail when post has nil user" do post = create_post post.update!(user: nil) PostAction.act(codinghorror, post, PostActionType.types[:spam], take_action: true) post.reload expect(post.hidden).to eq(true) end it "hide tl0 posts that are flagged as spam by a tl3 user" do newuser = Fabricate(:newuser) post = create_post(user: newuser) Discourse.stubs(:site_contact_user).returns(admin) PostAction.act(Fabricate(:leader), post, PostActionType.types[:spam]) post.reload expect(post.hidden).to eq(true) expect(post.hidden_at).to be_present expect(post.hidden_reason_id).to eq(Post.hidden_reasons[:flagged_by_tl3_user]) end it "hide non-tl4 posts that are flagged by a tl4 user" do SiteSetting.site_contact_username = admin.username post_action_type = PostActionType.types[:spam] tl4_user = Fabricate(:trust_level_4) user = Fabricate(:leader) post = create_post(user: user) PostAction.act(tl4_user, post, post_action_type) post.reload expect(post.hidden).to be_truthy expect(post.hidden_at).to be_present expect(post.hidden_reason_id).to eq(Post.hidden_reasons[:flagged_by_tl4_user]) post = create_post(user: user) PostAction.act(Fabricate(:leader), post, post_action_type) post.reload expect(post.hidden).to be_falsey post = create_post(user: user) PostAction.act(Fabricate(:moderator), post, post_action_type) post.reload expect(post.hidden).to be_falsey user = Fabricate(:trust_level_4) post = create_post(user: user) PostAction.act(tl4_user, post, post_action_type) post.reload expect(post.hidden).to be_falsey end it "can flag the topic instead of a post" do post1 = create_post _post2 = create_post(topic: post1.topic) post_action = PostAction.act(Fabricate(:user), post1, PostActionType.types[:spam], flag_topic: true) expect(post_action.targets_topic).to eq(true) end it "will flag the first post if you flag a topic but there is only one post in the topic" do post = create_post post_action = PostAction.act(Fabricate(:user), post, PostActionType.types[:spam], flag_topic: true) expect(post_action.targets_topic).to eq(false) expect(post_action.post_id).to eq(post.id) end it "will unhide the post when a moderator undos the flag on which s/he took action" do Discourse.stubs(:site_contact_user).returns(admin) post = create_post PostAction.act(moderator, post, PostActionType.types[:spam], take_action: true) post.reload expect(post.hidden).to eq(true) PostAction.remove_act(moderator, post, PostActionType.types[:spam]) post.reload expect(post.hidden).to eq(false) end context "topic auto closing" do let(:topic) { Fabricate(:topic) } let(:post1) { create_post(topic: topic) } let(:post2) { create_post(topic: topic) } let(:post3) { create_post(topic: topic) } let(:flagger1) { Fabricate(:user) } let(:flagger2) { Fabricate(:user) } before do SiteSetting.flags_required_to_hide_post = 0 SiteSetting.num_flags_to_close_topic = 3 SiteSetting.num_flaggers_to_close_topic = 2 SiteSetting.num_hours_to_close_topic = 1 end it "will automatically pause a topic due to large community flagging" do # reaching `num_flaggers_to_close_topic` isn't enough [flagger1, flagger2].each do |flagger| PostAction.act(flagger, post1, PostActionType.types[:inappropriate]) end expect(topic.reload.closed).to eq(false) # clean up PostAction.where(post: post1).delete_all # reaching `num_flags_to_close_topic` isn't enough [post1, post2, post3].each do |post| PostAction.act(flagger1, post, PostActionType.types[:inappropriate]) end expect(topic.reload.closed).to eq(false) # clean up PostAction.where(post: [post1, post2, post3]).delete_all # reaching both should close the topic [flagger1, flagger2].each do |flagger| [post1, post2, post3].each do |post| PostAction.act(flagger, post, PostActionType.types[:inappropriate]) end end expect(topic.reload.closed).to eq(true) topic_status_update = TopicTimer.last expect(topic_status_update.topic).to eq(topic) expect(topic_status_update.execute_at).to be_within(1.second).of(1.hour.from_now) expect(topic_status_update.status_type).to eq(TopicTimer.types[:open]) end context "on a staff post" do let(:staff_user) { Fabricate(:user, moderator: true) } let(:topic) { Fabricate(:topic, user: staff_user) } it "will not close topics opened by staff" do [flagger1, flagger2].each do |flagger| [post1, post2, post3].each do |post| PostAction.act(flagger, post, PostActionType.types[:inappropriate]) end end expect(topic.reload.closed).to eq(false) end end it "will keep the topic in closed status until the community flags are handled" do freeze_time SiteSetting.num_flaggers_to_close_topic = 1 SiteSetting.num_flags_to_close_topic = 1 post = Fabricate(:post, topic: topic) PostAction.act(flagger1, post, PostActionType.types[:spam]) expect(topic.reload.closed).to eq(true) timer = TopicTimer.last expect(timer.execute_at).to eq(1.hour.from_now) freeze_time timer.execute_at Jobs.expects(:enqueue_in).with( 1.hour.to_i, :toggle_topic_closed, topic_timer_id: timer.id, state: false ).returns(true) Jobs::ToggleTopicClosed.new.execute(topic_timer_id: timer.id, state: false) expect(topic.reload.closed).to eq(true) expect(timer.reload.execute_at).to eq(1.hour.from_now) freeze_time timer.execute_at SiteSetting.num_flaggers_to_close_topic = 10 SiteSetting.num_flags_to_close_topic = 10 Jobs::ToggleTopicClosed.new.execute(topic_timer_id: timer.id, state: false) expect(topic.reload.closed).to eq(false) end it "will reopen topic after the flags are auto handled" do freeze_time [flagger1, flagger2].each do |flagger| [post1, post2, post3].each do |post| PostAction.act(flagger, post, PostActionType.types[:inappropriate]) end end expect(topic.reload.closed).to eq(true) freeze_time 61.days.from_now Jobs::AutoQueueHandler.new.execute({}) Jobs::ToggleTopicClosed.new.execute(topic_timer_id: TopicTimer.last.id, state: false) expect(topic.reload.closed).to eq(false) end end end it "prevents user to act twice at the same time" do # flags are already being tested all_types_except_flags = PostActionType.types.except(PostActionType.flag_types_without_custom) all_types_except_flags.values.each do |action| expect do PostAction.act(eviltrout, post, action) PostAction.act(eviltrout, post, action) end.to raise_error(PostAction::AlreadyActed) end end describe ".create_message_for_post_action" do it "does not create a message when there is no message" do message_id = PostAction.create_message_for_post_action(Discourse.system_user, post, PostActionType.types[:spam], {}) expect(message_id).to be_nil end [:notify_moderators, :notify_user, :spam].each do |post_action_type| it "creates a message for #{post_action_type}" do message_id = PostAction.create_message_for_post_action(Discourse.system_user, post, PostActionType.types[post_action_type], message: "WAT") expect(message_id).to be_present end end it "should raise the right errors when it fails to create a post" do begin group = Group[:moderators] messageable_level = group.messageable_level group.update!(messageable_level: Group::ALIAS_LEVELS[:nobody]) expect do PostAction.create_message_for_post_action( Fabricate(:user), post, PostActionType.types[:notify_moderators], message: 'testing', ) end.to raise_error(ActiveRecord::RecordNotSaved) ensure group.update!(messageable_level: messageable_level) end end it "should succeed even with low max title length" do SiteSetting.max_topic_title_length = 50 post.topic.title = 'This is a test topic ' * 2 post.topic.save! message_id = PostAction.create_message_for_post_action(Discourse.system_user, post, PostActionType.types[:notify_moderators], message: "WAT") expect(message_id).to be_present end end describe ".lookup_for" do it "returns the correct map" do user = Fabricate(:user) post = Fabricate(:post) post_action = PostAction.create(user_id: user.id, post_id: post.id, post_action_type_id: 1) map = PostAction.lookup_for(user, [post.topic], post_action.post_action_type_id) expect(map).to eq(post.topic_id => [post.post_number]) end end describe "#add_moderator_post_if_needed" do it "should not add a moderator post when it's disabled" do post = create_post action = PostAction.act(moderator, post, PostActionType.types[:spam], message: "WAT") action.reload topic = action.related_post.topic expect(topic.posts.count).to eq(1) SiteSetting.auto_respond_to_flag_actions = false PostAction.agree_flags!(post, admin) expect(action.user.user_stat.flags_agreed).to eq(1) topic.reload expect(topic.posts.count).to eq(1) end it "should create a notification in the related topic" do Jobs.run_immediately! post = Fabricate(:post) user = Fabricate(:user) action = PostAction.act(user, post, PostActionType.types[:spam], message: "WAT") topic = action.reload.related_post.topic expect(user.notifications.count).to eq(0) SiteSetting.auto_respond_to_flag_actions = true PostAction.agree_flags!(post, admin) expect(action.user.user_stat.flags_agreed).to eq(1) user_notifications = user.notifications expect(user_notifications.count).to eq(1) expect(user_notifications.last.topic).to eq(topic) end it "should not add a moderator post when post is flagged via private message" do Jobs.run_immediately! post = Fabricate(:post) user = Fabricate(:user) action = PostAction.act(user, post, PostActionType.types[:notify_user], message: "WAT") action.reload.related_post.topic expect(user.notifications.count).to eq(0) SiteSetting.auto_respond_to_flag_actions = true PostAction.agree_flags!(post, admin) expect(action.user.user_stat.flags_agreed).to eq(0) user_notifications = user.notifications expect(user_notifications.count).to eq(0) end end describe "rate limiting" do def limiter(tl) user = Fabricate.build(:user) user.trust_level = tl action = PostAction.new(user: user, post_action_type_id: PostActionType.types[:like]) action.post_action_rate_limiter end it "should scale up like limits depending on liker" do expect(limiter(0).max).to eq SiteSetting.max_likes_per_day expect(limiter(1).max).to eq SiteSetting.max_likes_per_day expect(limiter(2).max).to eq (SiteSetting.max_likes_per_day * SiteSetting.tl2_additional_likes_per_day_multiplier).to_i expect(limiter(3).max).to eq (SiteSetting.max_likes_per_day * SiteSetting.tl3_additional_likes_per_day_multiplier).to_i expect(limiter(4).max).to eq (SiteSetting.max_likes_per_day * SiteSetting.tl4_additional_likes_per_day_multiplier).to_i SiteSetting.tl2_additional_likes_per_day_multiplier = -1 expect(limiter(2).max).to eq SiteSetting.max_likes_per_day SiteSetting.tl2_additional_likes_per_day_multiplier = 0.8 expect(limiter(2).max).to eq SiteSetting.max_likes_per_day SiteSetting.tl2_additional_likes_per_day_multiplier = "bob" expect(limiter(2).max).to eq SiteSetting.max_likes_per_day end end describe '#is_flag?' do describe 'when post action is a flag' do it 'should return true' do PostActionType.notify_flag_types.each do |_type, id| post_action = PostAction.new( user: codinghorror, post_action_type_id: id ) expect(post_action.is_flag?).to eq(true) end end end describe 'when post action is not a flag' do it 'should return false' do post_action = PostAction.new( user: codinghorror, post_action_type_id: 99 ) expect(post_action.is_flag?).to eq(false) end end end describe "triggers Discourse events" do let(:post) { Fabricate(:post) } it 'flag created' do event = DiscourseEvent.track_events { PostAction.act(eviltrout, post, PostActionType.types[:spam]) }.last expect(event[:event_name]).to eq(:flag_created) end context "resolving flags" do before do @flag = PostAction.act(eviltrout, post, PostActionType.types[:spam]) end it 'flag agreed' do events = DiscourseEvent.track_events { PostAction.agree_flags!(post, moderator) }.last(2) expect(events[0][:event_name]).to eq(:flag_reviewed) expect(events[1][:event_name]).to eq(:flag_agreed) expect(events[1][:params].first).to eq(@flag) end it 'flag disagreed' do events = DiscourseEvent.track_events { PostAction.clear_flags!(post, moderator) }.last(2) expect(events[0][:event_name]).to eq(:flag_reviewed) expect(events[1][:event_name]).to eq(:flag_disagreed) expect(events[1][:params].first).to eq(@flag) end it 'flag deferred' do events = DiscourseEvent.track_events { PostAction.defer_flags!(post, moderator) }.last(2) expect(events[0][:event_name]).to eq(:flag_reviewed) expect(events[1][:event_name]).to eq(:flag_deferred) expect(events[1][:params].first).to eq(@flag) end end end end