discourse/plugins/chat/spec/lib/chat_review_queue_spec.rb
Martin Brennan 766bcbc684
FIX: Add editing user ids to ChatMessage and ChatMessageRevision (#18877)
This commit adds last_editor_id to ChatMessage for parity with Post in
core, as well as adding user_id to the ChatMessageRevision record since
we need to know who is making edits and revisions to messages, in case
in future we want to allow more than just the current user to edit chat
messages. The backfill for data here simply uses the record's creating
user ID, but in future if we allow other people to edit the messages it
will use their ID.
2022-11-07 09:04:47 +10: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