discourse/spec/models/post_action_spec.rb
chapoi e52bbc1230
UX/DEV: Review queue redesign fixes (#20239)
* UX: add type tag and design update

* UX: clarify status copy in reviewQ

* DEV: switch to selectKit

* UX: color approve/reject buttons in RQ

* DEV: regroup actions

* UX: add type tag and design update

* UX: clarify status copy in reviewQ

* Join questions for flagged post with "or" with new I18n function
* Move ReviewableScores component out of context
* Add CSS classes to reviewable-item based on human type

* UX: add table header for scoring

* UX: don't display % score

* UX: prefix modifier class with dash

* UX: reviewQ flag table styling

* UX: consistent use of ignore icon

* DEV: only show context question on pending status

* UX: only show table headers on pending status

* DEV: reviewQ regroup actions for hidden posts

* UX: reviewQ > approve/reject buttons

* UX: reviewQ add fadeout

* UX: reviewQ styling

* DEV: move scores back into component

* UX: reviewQ mobile styling

* UX: score table on mobile

* UX: reviewQ > move meta info outside table

* UX: reviewQ > score layout fixes

* DEV: readd `agree_and_keep` and fix the spec tests.

* Fix the spec tests

* fix the quint test

* DEV: readd deleting replies

* UX: reviewQ copy tweaks

* DEV: readd test for ignore + delete replies

* Remove old

* FIX: Add perform_ignore back in for backwards compat

* DEV: add an action alias `ignore` for `ignore_and_do_nothing`.

---------

Co-authored-by: Martin Brennan <martin@discourse.org>
Co-authored-by: Vinoth Kannan <svkn.87@gmail.com>
2023-03-02 16:40:53 +01:00

991 lines
35 KiB
Ruby

# frozen_string_literal: true
RSpec.describe PostAction do
it { is_expected.to rate_limit }
fab!(:moderator) { Fabricate(:moderator) }
fab!(:codinghorror) { Fabricate(:coding_horror) }
fab!(:eviltrout) { Fabricate(:evil_trout) }
fab!(:admin) { Fabricate(:admin) }
fab!(:post) { Fabricate(: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
Group.refresh_automatic_groups!
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) { Fabricate(:group_user) }
let(:group) { group_user.group }
before do
SiteSetting.enable_category_group_moderation = true
group.update!(messageable_level: Group::ALIAS_LEVELS[:nobody])
post.topic.category.update!(reviewable_by_group_id: group.id)
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
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)
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)
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)
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)
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)
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)
post = create_post(user: newuser)
Discourse.stubs(:site_contact_user).returns(admin)
PostActionCreator.spam(Fabricate(:leader), 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),
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),
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) { Fabricate(:topic) }
let(:post1) { create_post(topic: topic) }
let(:post2) { create_post(topic: topic) }
let(:post3) { create_post(topic: topic) }
fab!(:flagger1) { Fabricate(:user) }
fab!(:flagger2) { Fabricate(:user) }
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
it "prevents user to act twice at the same time" do
Group.refresh_automatic_groups!
# flags are already being tested
all_types_except_flags =
PostActionType.types.except(*PostActionType.flag_types_without_custom.keys)
all_types_except_flags.values.each do |action|
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
%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 "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)
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
end