This commit main goal was to comply with Zeitwerk and properly rely on autoloading. To achieve this, most resources have been namespaced under the `Chat` module. - Given all models are now namespaced with `Chat::` and would change the stored types in DB when using polymorphism or STI (single table inheritance), this commit uses various Rails methods to ensure proper class is loaded and the stored name in DB is unchanged, eg: `Chat::Message` model will be stored as `"ChatMessage"`, and `"ChatMessage"` will correctly load `Chat::Message` model. - Jobs are now using constants only, eg: `Jobs::Chat::Foo` and should only be enqueued this way Notes: - This commit also used this opportunity to limit the number of registered css files in plugin.rb - `discourse_dev` support has been removed within this commit and will be reintroduced later <!-- NOTE: All pull requests should have tests (rspec in Ruby, qunit in JavaScript). If your code does not include test coverage, please include an explanation of why it was omitted. -->
441 lines
15 KiB
Ruby
441 lines
15 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "rails_helper"
|
|
|
|
describe Chat::ReviewQueue 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 = Chat::ReviewableMessage.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 = Chat::ReviewableMessage.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 = Chat::ReviewableMessage.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 = Chat::ReviewableMessage.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::MessageUpdater.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 = Chat::ReviewableMessage.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 = Chat::ReviewableMessage.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 =
|
|
Chat::ReviewableMessage.includes(:reviewable_scores).find_by(target_id: 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 =
|
|
Chat::ReviewableMessage.includes(:reviewable_scores).find_by(target_id: 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 = Chat::ReviewableMessage.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 = Chat::ReviewableMessage.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 = Chat::ReviewableMessage.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
|