# frozen_string_literal: true RSpec.describe PostAction do it { is_expected.to rate_limit } fab!(:moderator) { Fabricate(:moderator, refresh_auto_groups: true) } fab!(:codinghorror) { Fabricate(:coding_horror, refresh_auto_groups: true) } fab!(:eviltrout) { Fabricate(:evil_trout, refresh_auto_groups: true) } fab!(:admin) fab!(:post) fab!(:second_post) { Fabricate(:post, topic: post.topic) } def value_for(user_id, dt) GivenDailyLike.find_for(user_id, dt).pluck(:likes_given)[0] || 0 end it "disallows the same action from happening twice" do PostAction.create(user: eviltrout, post: post, post_action_type_id: PostActionType.types[:like]) pa = PostAction.new(user: eviltrout, post: post, post_action_type_id: PostActionType.types[:like]) expect(pa).not_to be_valid end describe "messaging" do it "notifies moderators (integration test)" do post = create_post mod = moderator result = PostActionCreator.notify_moderators(codinghorror, post, "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(result.post_action.related_post_id).to eq(posts[0].id.to_i) expect(result.reviewable_score.meta_topic_id).to eq(posts[0].topic_id) 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).pick(:notification_level)).to eq( TopicUser.notification_levels[:tracking], ) expect(topic.topic_users.where(user_id: codinghorror.id).pick(:notification_level)).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 result.post_action.reload expect(result.post_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) result.reviewable.perform(admin, :agree_and_keep) topic.reload expect(topic.posts.count).to eq(2) # Clearing the flags should not post an automated status message result = PostActionCreator.notify_moderators(mod, post, "another special message") result.reviewable.perform(admin, :disagree) topic.reload expect(topic.posts.count).to eq(2) # Acting on the flag should post an automated status message another_post = create_post result = PostActionCreator.notify_moderators(codinghorror, another_post, "foobar") topic = result.post_action.related_post.topic expect(topic.posts.count).to eq(1) result.reviewable.perform(admin, :agree_and_keep) topic.reload expect(topic.posts.count).to eq(2) expect(topic.posts.last.post_type).to eq(Post.types[:moderator_action]) expect(topic.message_archived?(mod)).to eq(true) end context "with category group moderators" do fab!(:group_user) let(:group) { group_user.group } before do SiteSetting.enable_category_group_moderation = true group.update!(messageable_level: Group::ALIAS_LEVELS[:nobody]) Fabricate(:category_moderation_group, category: post.topic.category, group:) end it "notifies via pm" do result = PostActionCreator.notify_moderators(codinghorror, post, "this is my special long message") readable_by_groups = result.reviewable_score.meta_topic.topic_allowed_groups.map(&:group_id) expect(readable_by_groups).to include(group.id) end 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) PostActionCreator.like(moderator, post) PostActionCreator.like(codinghorror, second_post) 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 end end describe "undo/redo repeatedly" do it "doesn't create a second action for the same user/type" do PostActionCreator.like(codinghorror, post) PostActionDestroyer.destroy(codinghorror, post, :like) PostActionCreator.like(codinghorror, post) expect(PostAction.where(post: post).with_deleted.count).to eq(1) PostActionDestroyer.destroy(codinghorror, post, :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 { PostActionNotifier.enable } it "should generate and remove notifications correctly" do PostActionCreator.like(codinghorror, post) 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]) PostActionDestroyer.destroy(codinghorror, post, :like) expect(Notification.count).to eq(0) PostActionCreator.like(codinghorror, post) 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 PostActionCreator.like(codinghorror, post) end.to_not change { Notification.count } end it "notifies on likes correctly" do SiteSetting.post_undo_action_window_mins = 120 PostActionCreator.like(eviltrout, post) PostActionCreator.like(admin, post) # 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 PostActionCreator.like(admin2, post) 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 PostActionDestroyer.destroy(eviltrout, post, :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) PostActionCreator.like(admin3, post) freeze_time 2.days.from_now admin4 = Fabricate(:admin) PostActionCreator.like(admin4, post) # first happened 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 fab!(:liker) { Fabricate(:user) } fab!(:liker2) { Fabricate(:user) } fab!(:likee) { Fabricate(:user) } it "can be disabled" do SiteSetting.notification_consolidation_threshold = 0 expect do PostActionCreator.like(liker, Fabricate(:post, user: likee)) end.to change { likee.reload.notifications.count }.by(1) SiteSetting.notification_consolidation_threshold = 1 expect do PostActionCreator.like(liker, Fabricate(:post, user: likee)) 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.notification_consolidation_threshold = 2 expect do 3.times { PostActionCreator.like(liker, Fabricate(:post, user: likee)) } 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 { PostActionCreator.like(liker, Fabricate(:post, user: likee)) } 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 PostActionCreator.like(Fabricate(:user), Fabricate(:post, user: likee)) 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 PostActionCreator.like(liker, Fabricate(:post, user: likee)) 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.notification_consolidation_threshold = 2 post = Fabricate(:post, user: likee) expect do [liker2, liker].each { |user| PostActionCreator.like(user, post) } 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 { PostActionCreator.like(liker, Fabricate(:post, user: likee)) } end.to change { likee.reload.notifications.count }.by(2) expect(likee.notifications.pluck(:notification_type).uniq).to contain_exactly( Notification.types[:liked], ) expect do PostActionCreator.like(liker, Fabricate(:post, user: likee)) 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 PostActionCreator.like(mutee, post) 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 PostActionCreator.like(codinghorror, post) end.to_not change { Notification.count } end it "should generate a notification if liker is an admin irregardless of \ muting" do MutedUser.create!(user_id: post.user.id, muted_user_id: admin.id) expect do PostActionCreator.like(admin, post) 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 not increase topic like count when liking a whisper" do SiteSetting.whispers_allowed_groups = "#{Group::AUTO_GROUPS[:staff]}" post.revise(admin, post_type: Post.types[:whisper]) PostActionCreator.like(admin, post) expect(post.reload.like_count).to eq(1) expect(post.topic.like_count).to eq(0) end it "should increase the `like_count` and `like_score` when a user likes something" do freeze_time Date.today PostActionCreator.like(codinghorror, post) 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 PostActionCreator.like(moderator, post) 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 PostActionDestroyer.destroy(codinghorror, post, :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) PostActionDestroyer.destroy(moderator, post, :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) PostActionCreator.like(codinghorror, post) expect(value_for(codinghorror.id, Date.today)).to eq(1) PostActionType.types.each do |type_name, type_id| post = Fabricate(:post) PostActionCreator.create(codinghorror, post, type_name) 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}" PostActionDestroyer.new(codinghorror, post, type_id).perform 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 before { SiteSetting.flag_post_allowed_groups = "1|2|11" } it "does not allow you to flag stuff twice, even if the reason is different" do expect(PostActionCreator.spam(eviltrout, post)).to be_success expect(PostActionCreator.off_topic(eviltrout, post)).to be_failed end it "allows you to flag stuff again if your previous flag was removed" do PostActionCreator.spam(eviltrout, post) PostActionDestroyer.destroy(eviltrout, post, :spam) expect(PostActionCreator.spam(eviltrout, post)).to be_success end it "should update counts when you clear flags" do reviewable = PostActionCreator.spam(eviltrout, post).reviewable expect(post.reload.spam_count).to eq(1) reviewable.perform(Discourse.system_user, :disagree) expect(post.reload.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) Reviewable.set_priorities(high: 2.0) SiteSetting.hide_post_sensitivity = Reviewable.sensitivities[:low] Discourse.stubs(:site_contact_user).returns(admin) PostActionCreator.spam(eviltrout, post) PostActionCreator.spam(Fabricate(:walter_white), post) 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) Reviewable.set_priorities(high: 8.0) SiteSetting.hide_post_sensitivity = Reviewable.sensitivities[:low] Discourse.stubs(:site_contact_user).returns(admin) PostActionCreator.spam(eviltrout, post) PostActionCreator.spam(Fabricate(:admin), post) post.reload expect(post.hidden).to eq(true) expect(post.hidden_at).to be_present end it "will not trigger auto hide on like" do mod = Fabricate(:moderator) post = Fabricate(:post, user: mod) result = PostActionCreator.spam(eviltrout, post) result.reviewable.update!(score: 1000.0) PostActionCreator.like(Fabricate(:admin), post) post.reload expect(post.hidden).to eq(false) end it "should follow the rules for automatic hiding workflow" do post = create_post walterwhite = Fabricate(:walter_white, refresh_auto_groups: true) Reviewable.set_priorities(high: 3.0) SiteSetting.hide_post_sensitivity = Reviewable.sensitivities[:low] Discourse.stubs(:site_contact_user).returns(admin) PostActionCreator.spam(eviltrout, post) PostActionCreator.spam(walterwhite, post) 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) expect(post.topic.visibility_reason_id).to eq( Topic.visibility_reasons[:op_flag_threshold_reached], ) 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) expect(post.topic.visibility_reason_id).to eq(Topic.visibility_reasons[:op_unhidden]) PostActionCreator.spam(eviltrout, post) PostActionCreator.off_topic(walterwhite, post) 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) expect(post.topic.visibility_reason_id).to eq( Topic.visibility_reasons[:op_flag_threshold_reached], ) 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.reload.visible).to eq(false) expect(post.topic.visibility_reason_id).to eq( Topic.visibility_reasons[:op_flag_threshold_reached], ) end it "doesn't fail when post has nil user" do post = create_post post.update!(user: nil) PostActionCreator.new(moderator, post, PostActionType.types[:spam], take_action: true).perform 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, refresh_auto_groups: true) post = create_post(user: newuser) Discourse.stubs(:site_contact_user).returns(admin) PostActionCreator.spam(Fabricate(:leader, refresh_auto_groups: true), post) 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 "can flag the topic instead of a post" do post1 = create_post create_post(topic: post1.topic) result = PostActionCreator.new( Fabricate(:user, refresh_auto_groups: true), post1, PostActionType.types[:spam], flag_topic: true, ).perform expect(result.post_action.targets_topic).to eq(true) expect(result.reviewable.payload["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 result = PostActionCreator.new( Fabricate(:user, refresh_auto_groups: true), post, PostActionType.types[:spam], flag_topic: true, ).perform expect(result.post_action.targets_topic).to eq(false) expect(result.post_action.post_id).to eq(post.id) expect(result.reviewable.payload["targets_topic"]).to eq(false) end it "will unhide the post when a moderator undoes the flag on which s/he took action" do Discourse.stubs(:site_contact_user).returns(admin) post = create_post PostActionCreator.new(moderator, post, PostActionType.types[:spam], take_action: true).perform post.reload expect(post.hidden).to eq(true) PostActionDestroyer.destroy(moderator, post, :spam) post.reload expect(post.hidden).to eq(false) end context "with topic auto closing" do fab!(:topic) let(:post1) { create_post(topic: topic) } let(:post2) { create_post(topic: topic) } let(:post3) { create_post(topic: topic) } fab!(:flagger1) { Fabricate(:user, refresh_auto_groups: true) } fab!(:flagger2) { Fabricate(:user, refresh_auto_groups: true) } before do SiteSetting.hide_post_sensitivity = Reviewable.sensitivities[:disabled] Reviewable.set_priorities(high: 4.5) SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivities[:low] 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 freeze_time # reaching `num_flaggers_to_close_topic` isn't enough [flagger1, flagger2].each { |flagger| PostActionCreator.inappropriate(flagger, post1) } 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 { |post| PostActionCreator.inappropriate(flagger1, post) } 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 { |post| PostActionCreator.inappropriate(flagger, post) } 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 eq_time(1.hour.from_now) expect(topic_status_update.status_type).to eq(TopicTimer.types[:open]) end context "when on a staff post" do fab!(:staff_user) { Fabricate(:user, moderator: true) } fab!(:topic) { Fabricate(:topic, user: staff_user) } it "will not close topics opened by staff" do [flagger1, flagger2].each do |flagger| [post1, post2, post3].each { |post| PostActionCreator.inappropriate(flagger, post) } 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 Reviewable.set_priorities(high: 0.5) SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivities[:low] post = Fabricate(:post, topic: topic) PostActionCreator.spam(flagger1, post) expect(topic.reload.closed).to eq(true) timer = TopicTimer.last expect(timer.execute_at).to eq_time(1.hour.from_now) freeze_time timer.execute_at Jobs::OpenTopic.new.execute(topic_timer_id: timer.id) expect(topic.reload.closed).to eq(true) expect(timer.reload.execute_at).to eq_time(1.hour.from_now) freeze_time timer.execute_at SiteSetting.num_flaggers_to_close_topic = 10 Reviewable.set_priorities(high: 10.0) SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivities[:low] 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 { |post| PostActionCreator.inappropriate(flagger, post) } 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 # flags are already being tested all_types_except_flags = PostActionType.types.except(*PostActionType.flag_types_without_additional_message.keys) all_types_except_flags.values.each do |action| it "prevents user to act twice at the same time" do expect(PostActionCreator.new(eviltrout, post, action).perform).to be_success expect(PostActionCreator.new(eviltrout, post, action).perform).to be_failed end end describe "messages" do it "does not create a message when there is no message" do result = PostActionCreator.spam(Discourse.system_user, post) expect(result).to be_success expect(result.post_action.related_post_id).to be_nil expect(result.reviewable_score.meta_topic_id).to be_nil end it "does not create a message for custom flag when message is not required" do flag_without_message = Fabricate(:flag, name: "flag without message", notify_type: true, require_message: false) result = PostActionCreator.new( Discourse.system_user, post, PostActionType.types[:flag_without_message], message: "WAT", ).perform expect(result).to be_success expect(result.post_action.related_post_id).to be_nil expect(result.reviewable_score.meta_topic_id).to be_nil flag_without_message.destroy! end %i[notify_moderators notify_user spam].each do |post_action_type| it "creates a message for #{post_action_type}" do result = PostActionCreator.new( Discourse.system_user, post, PostActionType.types[post_action_type], message: "WAT", ).perform expect(result).to be_success expect(result.post_action.related_post_id).to be_present end end it "creates a message for custom flags when message is required" do flag_with_message = Fabricate(:flag, name: "flag with message", notify_type: true, require_message: true) result = PostActionCreator.new( Discourse.system_user, post, PostActionType.types[:flag_with_message], message: "WAT", ).perform expect(result).to be_success expect(result.post_action.related_post_id).to be_present expect(result.reviewable_score.meta_topic_id).to be_present flag_with_message.destroy! end it "should raise the right errors when it fails to create a post" do user = Fabricate(:user) UserSilencer.new(user, Discourse.system_user).silence result = PostActionCreator.notify_moderators(user, post, "testing") expect(result).to be_failed 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! result = PostActionCreator.notify_moderators(Discourse.system_user, post, "WAT") expect(result).to be_success expect(result.post_action.related_post_id).to be_present end end describe ".lookup_for" do it "returns the correct map" do user = Fabricate(:user) post_action = PostActionCreator.create(user, post, :like).post_action 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 result = PostActionCreator.create(moderator, post, :spam, message: "WAT") topic = result.post_action.related_post.topic expect(topic.posts.count).to eq(1) SiteSetting.auto_respond_to_flag_actions = false result.reviewable.perform(admin, :agree_and_keep) expect(topic.reload.posts.count).to eq(1) end it "should create a notification in the related topic" do Jobs.run_immediately! user = Fabricate(:user, refresh_auto_groups: true) stub_image_size result = PostActionCreator.create(user, post, :spam, message: "WAT") topic = result.post_action.related_post.topic reviewable = result.reviewable expect(user.notifications.count).to eq(0) SiteSetting.auto_respond_to_flag_actions = true reviewable.perform(admin, :agree_and_keep) user_notifications = user.notifications expect(user_notifications.last.topic).to eq(topic) end skip "should not add a moderator post when post is flagged via private message" do Jobs.run_immediately! user = Fabricate(:user) result = PostActionCreator.create(user, post, :notify_user, message: "WAT") action = result.post_action action.reload.related_post.topic expect(user.notifications.count).to eq(0) SiteSetting.auto_respond_to_flag_actions = true result.reviewable.perform(admin, :agree_and_keep) expect(user.reload.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, type) user = Fabricate.build(:user) user.trust_level = tl action = PostAction.new(user: user, post_action_type_id: PostActionType.types[type]) action.post_action_rate_limiter end it "should scale up likes limits depending on trust level" do expect(limiter(0, :like).max).to eq SiteSetting.max_likes_per_day expect(limiter(1, :like).max).to eq SiteSetting.max_likes_per_day expect(limiter(2, :like).max).to eq ( SiteSetting.max_likes_per_day * SiteSetting.tl2_additional_likes_per_day_multiplier ).to_i expect(limiter(3, :like).max).to eq ( SiteSetting.max_likes_per_day * SiteSetting.tl3_additional_likes_per_day_multiplier ).to_i expect(limiter(4, :like).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, :like).max).to eq SiteSetting.max_likes_per_day SiteSetting.tl2_additional_likes_per_day_multiplier = 0.8 expect(limiter(2, :like).max).to eq SiteSetting.max_likes_per_day SiteSetting.tl2_additional_likes_per_day_multiplier = "bob" expect(limiter(2, :like).max).to eq SiteSetting.max_likes_per_day end it "should scale up flag limits depending on trust level" do %i[off_topic inappropriate spam notify_moderators].each do |type| SiteSetting.tl2_additional_flags_per_day_multiplier = 1.5 expect(limiter(0, type).max).to eq SiteSetting.max_flags_per_day expect(limiter(1, type).max).to eq SiteSetting.max_flags_per_day expect(limiter(2, type).max).to eq ( SiteSetting.max_flags_per_day * SiteSetting.tl2_additional_flags_per_day_multiplier ).to_i expect(limiter(3, type).max).to eq ( SiteSetting.max_flags_per_day * SiteSetting.tl3_additional_flags_per_day_multiplier ).to_i expect(limiter(4, type).max).to eq ( SiteSetting.max_flags_per_day * SiteSetting.tl4_additional_flags_per_day_multiplier ).to_i SiteSetting.tl2_additional_flags_per_day_multiplier = -1 expect(limiter(2, type).max).to eq SiteSetting.max_flags_per_day SiteSetting.tl2_additional_flags_per_day_multiplier = 0.8 expect(limiter(2, type).max).to eq SiteSetting.max_flags_per_day SiteSetting.tl2_additional_flags_per_day_multiplier = "bob" expect(limiter(2, type).max).to eq SiteSetting.max_flags_per_day end 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 it "triggers a flag_created event" do event = DiscourseEvent.track(:flag_created) { PostActionCreator.spam(eviltrout, post) } expect(event).to be_present end context "when resolving flags" do let(:result) { PostActionCreator.spam(eviltrout, post) } let(:post_action) { result.post_action } let(:reviewable) { result.reviewable } it "creates events for agreed" do events = DiscourseEvent.track_events { reviewable.perform(moderator, :agree_and_keep) } reviewed_event = events.find { |e| e[:event_name] == :flag_reviewed } expect(reviewed_event).to be_present event = events.find { |e| e[:event_name] == :flag_agreed } expect(event).to be_present expect(event[:params]).to eq([post_action]) end it "creates events for disagreed" do events = DiscourseEvent.track_events { reviewable.perform(moderator, :disagree) } reviewed_event = events.find { |e| e[:event_name] == :flag_reviewed } expect(reviewed_event).to be_present event = events.find { |e| e[:event_name] == :flag_disagreed } expect(event).to be_present expect(event[:params]).to eq([post_action]) end it "creates events for ignored" do events = DiscourseEvent.track_events { reviewable.perform(moderator, :ignore_and_do_nothing) } reviewed_event = events.find { |e| e[:event_name] == :flag_reviewed } expect(reviewed_event).to be_present event = events.find { |e| e[:event_name] == :flag_deferred } expect(event).to be_present expect(event[:params]).to eq([post_action]) end end end describe "count_per_day_for_type" do before { PostActionCreator.create(eviltrout, post, :like) } it "returns the correct count" do expect(PostAction.count_per_day_for_type(PostActionType.types[:like])).to eq( Time.now.utc.to_date => 1, ) end it "returns the correct count when there are multiple actions" do PostActionCreator.create(codinghorror, post, :like) expect(PostAction.count_per_day_for_type(PostActionType.types[:like])).to eq( Time.now.utc.to_date => 2, ) end it "returns the correct count when there are multiple types" do PostActionCreator.create(eviltrout, post, :spam) expect(PostAction.count_per_day_for_type(PostActionType.types[:spam])).to eq( Time.now.utc.to_date => 1, ) end it "returns the correct count with group filter" do group = Fabricate(:group) group.add(codinghorror) PostActionCreator.create(codinghorror, post, :like) expect( PostAction.count_per_day_for_type(PostActionType.types[:like], { group_ids: [group.id] }), ).to eq(Time.now.utc.to_date => 1) end end end