mirror of
https://github.com/discourse/discourse.git
synced 2024-12-01 04:33:37 +08:00
766bcbc684
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.
440 lines
15 KiB
Ruby
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
|