discourse/plugins/chat/spec/lib/chat_review_queue_spec.rb
2023-01-07 11:11:37 +00:00

440 lines
15 KiB
Ruby

# frozen_string_literal: true
require "rails_helper"
describe Chat::ChatReviewQueue do
fab!(:message_poster) { Fabricate(:user) }
fab!(:flagger) { Fabricate(:user) }
fab!(:chat_channel) { Fabricate(:category_channel) }
fab!(:message) { Fabricate(:chat_message, user: message_poster, chat_channel: chat_channel) }
fab!(:admin) { Fabricate(:admin) }
let(:guardian) { Guardian.new(flagger) }
let(:admin_guardian) { Guardian.new(admin) }
subject(:queue) { described_class.new }
before do
chat_channel.add(message_poster)
chat_channel.add(flagger)
Group.refresh_automatic_groups!
end
describe "#flag_message" do
it "raises an error when the user is not allowed to flag" do
UserSilencer.new(flagger).silence
expect { queue.flag_message(message, guardian, ReviewableScore.types[:spam]) }.to raise_error(
Discourse::InvalidAccess,
)
end
it "stores the message cooked content inside the reviewable" do
queue.flag_message(message, guardian, ReviewableScore.types[:off_topic])
reviewable = ReviewableChatMessage.last
expect(reviewable.payload["message_cooked"]).to eq(message.cooked)
end
context "when the user already flagged the post" do
let(:second_flag_result) do
queue.flag_message(message, guardian, ReviewableScore.types[:off_topic])
end
before { queue.flag_message(message, guardian, ReviewableScore.types[:spam]) }
it "returns an error" do
expect(second_flag_result).to include success: false,
errors: [I18n.t("chat.reviewables.message_already_handled")]
end
it "returns an error when trying to use notify_moderators and the previous flag is still pending" do
notify_moderators_result =
queue.flag_message(
message,
guardian,
ReviewableScore.types[:notify_moderators],
message: "Look at this please, moderators",
)
expect(notify_moderators_result).to include success: false,
errors: [I18n.t("chat.reviewables.message_already_handled")]
end
end
context "when a different user already flagged the post" do
let(:second_flag_result) { queue.flag_message(message, admin_guardian, second_flag_type) }
before { queue.flag_message(message, guardian, ReviewableScore.types[:spam]) }
it "appends a new score to the existing reviewable" do
second_flag_result =
queue.flag_message(message, admin_guardian, ReviewableScore.types[:off_topic])
expect(second_flag_result).to include success: true
reviewable = ReviewableChatMessage.find_by(target: message)
scores = reviewable.reviewable_scores
expect(scores.size).to eq(2)
expect(scores.map(&:reviewable_score_type)).to contain_exactly(
*ReviewableScore.types.slice(:off_topic, :spam).values,
)
end
it "returns an error when someone already used the same flag type" do
second_flag_result =
queue.flag_message(message, admin_guardian, ReviewableScore.types[:spam])
expect(second_flag_result).to include success: false,
errors: [I18n.t("chat.reviewables.message_already_handled")]
end
end
context "when a flags exists but staff already handled it" do
let(:second_flag_result) do
queue.flag_message(message, guardian, ReviewableScore.types[:off_topic])
end
before do
queue.flag_message(message, guardian, ReviewableScore.types[:spam])
reviewable = ReviewableChatMessage.last
reviewable.perform(admin, :ignore)
end
it "raises an error when we are inside the cooldown window" do
expect(second_flag_result).to include success: false,
errors: [I18n.t("chat.reviewables.message_already_handled")]
end
it "allows the user to re-flag after the cooldown period" do
reviewable = ReviewableChatMessage.last
reviewable.update!(updated_at: (SiteSetting.cooldown_hours_until_reflag.to_i + 1).hours.ago)
expect(second_flag_result).to include success: true
end
it "ignores the cooldown window when the message is edited" do
Chat::ChatMessageUpdater.update(
guardian: Guardian.new(message.user),
chat_message: message,
new_content: "I'm editing this message. Please flag it.",
)
expect(second_flag_result).to include success: true
end
it "ignores the cooldown window when using the notify_moderators flag type" do
notify_moderators_result =
queue.flag_message(
message,
guardian,
ReviewableScore.types[:notify_moderators],
message: "Look at this please, moderators",
)
expect(notify_moderators_result).to include success: true
end
end
it "publishes a message to the flagger" do
messages =
MessageBus
.track_publish { queue.flag_message(message, guardian, ReviewableScore.types[:spam]) }
.map(&:data)
self_flag_msg = messages.detect { |m| m["type"] == "self_flagged" }
expect(self_flag_msg["user_flag_status"]).to eq(ReviewableScore.statuses[:pending])
expect(self_flag_msg["chat_message_id"]).to eq(message.id)
end
it "publishes a message to tell staff there is a new reviewable" do
messages =
MessageBus
.track_publish { queue.flag_message(message, guardian, ReviewableScore.types[:spam]) }
.map(&:data)
flag_msg = messages.detect { |m| m["type"] == "flag" }
new_reviewable = ReviewableChatMessage.find_by(target: message)
expect(flag_msg["chat_message_id"]).to eq(message.id)
expect(flag_msg["reviewable_id"]).to eq(new_reviewable.id)
end
let(:flag_message) { "I just flagged your chat message..." }
context "when creating a notify_user flag" do
it "creates a companion PM" do
queue.flag_message(
message,
guardian,
ReviewableScore.types[:notify_user],
message: flag_message,
)
pm_topic =
Topic.includes(:posts).find_by(user: guardian.user, archetype: Archetype.private_message)
pm_post = pm_topic.first_post
expect(pm_topic.allowed_users).to include(message.user)
expect(pm_topic.subtype).to eq(TopicSubtype.notify_user)
expect(pm_post.raw).to include(flag_message)
expect(pm_topic.title).to eq("Your chat message in \"#{chat_channel.title(message.user)}\"")
end
it "doesn't create a PM if there is no message" do
queue.flag_message(message, guardian, ReviewableScore.types[:notify_user])
pm_topic =
Topic.includes(:posts).find_by(user: guardian.user, archetype: Archetype.private_message)
expect(pm_topic).to be_nil
end
it "allow staff to tag PM as a warning" do
queue.flag_message(
message,
admin_guardian,
ReviewableScore.types[:notify_user],
message: flag_message,
is_warning: true,
)
expect(UserWarning.exists?(user: message.user)).to eq(true)
end
it "only allows staff members to send warnings" do
expect do
queue.flag_message(
message,
guardian,
ReviewableScore.types[:notify_user],
message: flag_message,
is_warning: true,
)
end.to raise_error(Discourse::InvalidAccess)
end
end
context "when creating a notify_moderators flag" do
it "creates a companion PM and gives moderators access to it" do
queue.flag_message(
message,
guardian,
ReviewableScore.types[:notify_moderators],
message: flag_message,
)
pm_topic =
Topic.includes(:posts).find_by(user: guardian.user, archetype: Archetype.private_message)
pm_post = pm_topic.first_post
expect(pm_topic.allowed_groups).to contain_exactly(Group[:moderators])
expect(pm_topic.subtype).to eq(TopicSubtype.notify_moderators)
expect(pm_post.raw).to include(flag_message)
expect(pm_topic.title).to eq(
"A chat message in \"#{chat_channel.title(message.user)}\" requires staff attention",
)
end
it "ignores the is_warning flag when notifying moderators" do
queue.flag_message(
message,
guardian,
ReviewableScore.types[:notify_moderators],
message: flag_message,
is_warning: true,
)
expect(UserWarning.exists?(user: message.user)).to eq(false)
end
end
context "when immediately taking action" do
it "agrees with the flag and deletes the chat message" do
queue.flag_message(
message,
admin_guardian,
ReviewableScore.types[:off_topic],
take_action: true,
)
reviewable = ReviewableChatMessage.find_by(target: message)
expect(reviewable.approved?).to eq(true)
expect(message.reload.trashed?).to eq(true)
end
it "publishes an when deleting the message" do
messages =
MessageBus
.track_publish do
queue.flag_message(
message,
admin_guardian,
ReviewableScore.types[:off_topic],
take_action: true,
)
end
.map(&:data)
delete_msg = messages.detect { |m| m[:type] == "delete" }
expect(delete_msg[:deleted_id]).to eq(message.id)
end
it "agrees with other flags on the same message" do
queue.flag_message(message, guardian, ReviewableScore.types[:off_topic])
reviewable = ReviewableChatMessage.includes(:reviewable_scores).find_by(target: message)
scores = reviewable.reviewable_scores
expect(scores.size).to eq(1)
expect(scores.all?(&:pending?)).to eq(true)
queue.flag_message(message, admin_guardian, ReviewableScore.types[:spam], take_action: true)
scores = reviewable.reload.reviewable_scores
expect(scores.size).to eq(2)
expect(scores.all?(&:agreed?)).to eq(true)
end
it "raises an exception if the user is not a staff member" do
expect do
queue.flag_message(
message,
guardian,
ReviewableScore.types[:off_topic],
take_action: true,
)
end.to raise_error(Discourse::InvalidAccess)
end
end
context "when queueing for review" do
it "sets a reason on the score" do
queue.flag_message(
message,
admin_guardian,
ReviewableScore.types[:off_topic],
queue_for_review: true,
)
reviewable = ReviewableChatMessage.includes(:reviewable_scores).find_by(target: message)
score = reviewable.reviewable_scores.first
expect(score.reason).to eq("chat_message_queued_by_staff")
end
it "only allows staff members to queue for review" do
expect do
queue.flag_message(
message,
guardian,
ReviewableScore.types[:off_topic],
queue_for_review: true,
)
end.to raise_error(Discourse::InvalidAccess)
end
end
context "when the auto silence threshold is met" do
it "silences the user" do
SiteSetting.chat_auto_silence_from_flags_duration = 1
flagger.update!(trust_level: TrustLevel[4]) # Increase Score due to TL Bonus.
queue.flag_message(message, guardian, ReviewableScore.types[:off_topic])
expect(message_poster.reload.silenced?).to eq(true)
end
it "does nothing if the new score is less than the auto-silence threshold" do
SiteSetting.chat_auto_silence_from_flags_duration = 50
queue.flag_message(message, guardian, ReviewableScore.types[:off_topic])
expect(message_poster.reload.silenced?).to eq(false)
end
it "does nothing if the silence duration is set to 0" do
SiteSetting.chat_auto_silence_from_flags_duration = 0
flagger.update!(trust_level: TrustLevel[4]) # Increase Score due to TL Bonus.
queue.flag_message(message, guardian, ReviewableScore.types[:off_topic])
expect(message_poster.reload.silenced?).to eq(false)
end
end
context "when flagging a DM" do
fab!(:dm_channel) { Fabricate(:direct_message_channel, users: [message_poster, flagger]) }
12.times do |i|
fab!("dm_message_#{i + 1}") do
Fabricate(
:chat_message,
user: message_poster,
chat_channel: dm_channel,
message: "This is my message number #{i + 1}. Hello chat!",
)
end
end
it "raises an exception when using the notify_moderators flag type" do
expect {
queue.flag_message(dm_message_1, guardian, ReviewableScore.types[:notify_moderators])
}.to raise_error(Discourse::InvalidParameters)
end
it "raises an exception when using the notify_user flag type" do
expect {
queue.flag_message(dm_message_1, guardian, ReviewableScore.types[:notify_user])
}.to raise_error(Discourse::InvalidParameters)
end
it "includes a transcript of the previous 10 message for the rest of the flags" do
queue.flag_message(dm_message_12, guardian, ReviewableScore.types[:off_topic])
reviewable = ReviewableChatMessage.last
expect(reviewable.target).to eq(dm_message_12)
transcript_post = Post.find_by(topic_id: reviewable.payload["transcript_topic_id"])
expect(transcript_post.cooked).to include(dm_message_2.message)
expect(transcript_post.cooked).to include(dm_message_5.message)
expect(transcript_post.cooked).not_to include(dm_message_1.message)
end
it "doesn't include a transcript if there a no previous messages" do
queue.flag_message(dm_message_1, guardian, ReviewableScore.types[:off_topic])
reviewable = ReviewableChatMessage.last
expect(reviewable.payload["transcript_topic_id"]).to be_nil
end
it "the transcript is only available to moderators and the system user" do
moderator = Fabricate(:moderator)
admin = Fabricate(:admin)
leader = Fabricate(:leader)
tl4 = Fabricate(:trust_level_4)
queue.flag_message(dm_message_12, guardian, ReviewableScore.types[:off_topic])
reviewable = ReviewableChatMessage.last
transcript_topic = Topic.find(reviewable.payload["transcript_topic_id"])
expect(guardian.can_see_topic?(transcript_topic)).to eq(false)
expect(Guardian.new(leader).can_see_topic?(transcript_topic)).to eq(false)
expect(Guardian.new(tl4).can_see_topic?(transcript_topic)).to eq(false)
expect(Guardian.new(dm_message_12.user).can_see_topic?(transcript_topic)).to eq(false)
expect(Guardian.new(moderator).can_see_topic?(transcript_topic)).to eq(true)
expect(Guardian.new(admin).can_see_topic?(transcript_topic)).to eq(true)
expect(Guardian.new(Discourse.system_user).can_see_topic?(transcript_topic)).to eq(true)
end
end
end
end