mirror of
https://github.com/discourse/discourse.git
synced 2025-05-25 19:29:34 +08:00

When a post is flagged due to matching watched words, it can be difficult to know what you're looking for, particularly if you have a lot of watched words built up over a long period of time. This change stores the list of matched words, and later displays them in the review queue, listing which Watched Words were responsible for the flag. Because watched words can change, this is recorded at the time the post is flagged. For posts that were flagged prior to this feature landing, it tries to guess the relevant words based on the current Watched Words set.
364 lines
11 KiB
Ruby
364 lines
11 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe WatchedWord do
|
|
fab!(:tl2_user) { Fabricate(:user, trust_level: TrustLevel[2]) }
|
|
fab!(:admin)
|
|
fab!(:moderator)
|
|
|
|
fab!(:topic)
|
|
fab!(:first_post) { Fabricate(:post, topic: topic) }
|
|
|
|
let(:require_approval_word) do
|
|
Fabricate(:watched_word, action: WatchedWord.actions[:require_approval])
|
|
end
|
|
let(:flag_word) { Fabricate(:watched_word, action: WatchedWord.actions[:flag]) }
|
|
let(:another_flag_word) { Fabricate(:watched_word, action: WatchedWord.actions[:flag]) }
|
|
let(:block_word) { Fabricate(:watched_word, action: WatchedWord.actions[:block]) }
|
|
let(:another_block_word) { Fabricate(:watched_word, action: WatchedWord.actions[:block]) }
|
|
|
|
before_all { WordWatcher.clear_cache! }
|
|
|
|
after { WordWatcher.clear_cache! }
|
|
|
|
context "with block" do
|
|
def should_block_post(manager)
|
|
expect {
|
|
result = manager.perform
|
|
expect(result).to_not be_success
|
|
expect(result.errors[:base]&.first).to eq(
|
|
I18n.t("contains_blocked_word", word: block_word.word),
|
|
)
|
|
}.to_not change { Post.count }
|
|
end
|
|
|
|
it "escapes the blocked word in error message" do
|
|
block_word = Fabricate(:watched_word, action: WatchedWord.actions[:block], word: "<a>")
|
|
manager =
|
|
NewPostManager.new(
|
|
tl2_user,
|
|
raw: "Want some #{block_word.word} for cheap?",
|
|
topic_id: topic.id,
|
|
)
|
|
result = manager.perform
|
|
expect(result).to_not be_success
|
|
expect(result.errors[:base]&.first).to eq(I18n.t("contains_blocked_word", word: "<a>"))
|
|
end
|
|
|
|
it "should prevent the post from being created" do
|
|
manager =
|
|
NewPostManager.new(
|
|
tl2_user,
|
|
raw: "Want some #{block_word.word} for cheap?",
|
|
topic_id: topic.id,
|
|
)
|
|
should_block_post(manager)
|
|
end
|
|
|
|
it "look at title too" do
|
|
manager =
|
|
NewPostManager.new(
|
|
tl2_user,
|
|
title: "We sell #{block_word.word} online",
|
|
raw: "Want some poutine for cheap?",
|
|
topic_id: topic.id,
|
|
)
|
|
should_block_post(manager)
|
|
end
|
|
|
|
it "should handle UTF-8 characters" do
|
|
block_word = Fabricate(:watched_word, action: WatchedWord.actions[:block], word: "abc")
|
|
manager =
|
|
NewPostManager.new(tl2_user, title: "Hello world", raw: "abcódef", topic_id: topic.id)
|
|
|
|
expect(manager.perform).to be_success
|
|
end
|
|
|
|
it "should block the post from admin" do
|
|
manager =
|
|
NewPostManager.new(
|
|
admin,
|
|
raw: "Want some #{block_word.word} for cheap?",
|
|
topic_id: topic.id,
|
|
)
|
|
should_block_post(manager)
|
|
end
|
|
|
|
it "should block the post from moderator" do
|
|
manager =
|
|
NewPostManager.new(
|
|
moderator,
|
|
raw: "Want some #{block_word.word} for cheap?",
|
|
topic_id: topic.id,
|
|
)
|
|
should_block_post(manager)
|
|
end
|
|
|
|
it "should block the post if it contains multiple blocked words" do
|
|
manager =
|
|
NewPostManager.new(
|
|
moderator,
|
|
raw: "Want some #{block_word.word} #{another_block_word.word} for cheap?",
|
|
topic_id: topic.id,
|
|
)
|
|
expect {
|
|
result = manager.perform
|
|
expect(result).to_not be_success
|
|
expect(result.errors[:base]&.first).to eq(
|
|
I18n.t(
|
|
"contains_blocked_words",
|
|
words: [block_word.word, another_block_word.word].sort.join(", "),
|
|
),
|
|
)
|
|
}.to_not change { Post.count }
|
|
end
|
|
|
|
it "should block in a private message too" do
|
|
manager =
|
|
NewPostManager.new(
|
|
tl2_user,
|
|
raw: "Want some #{block_word.word} for cheap?",
|
|
title: "this is a new title",
|
|
archetype: Archetype.private_message,
|
|
target_usernames: Fabricate(:user, trust_level: TrustLevel[2]).username,
|
|
)
|
|
should_block_post(manager)
|
|
end
|
|
|
|
it "blocks on revisions" do
|
|
post = Fabricate(:post, topic: Fabricate(:topic, user: tl2_user), user: tl2_user)
|
|
expect {
|
|
PostRevisor.new(post).revise!(
|
|
post.user,
|
|
{ raw: "Want some #{block_word.word} for cheap?" },
|
|
revised_at: post.updated_at + 10.seconds,
|
|
)
|
|
expect(post.errors).to be_present
|
|
post.reload
|
|
}.to_not change { post.raw }
|
|
end
|
|
end
|
|
|
|
context "with require_approval" do
|
|
it "should queue the post for approval" do
|
|
manager =
|
|
NewPostManager.new(
|
|
tl2_user,
|
|
raw: "My dog's name is #{require_approval_word.word}.",
|
|
topic_id: topic.id,
|
|
)
|
|
result = manager.perform
|
|
expect(result.action).to eq(:enqueued)
|
|
expect(result.reason).to eq(:watched_word)
|
|
end
|
|
|
|
it "looks at title too" do
|
|
manager =
|
|
NewPostManager.new(
|
|
tl2_user,
|
|
title: "You won't believe these #{require_approval_word.word} dog names!",
|
|
raw: "My dog's name is Porkins.",
|
|
topic_id: topic.id,
|
|
)
|
|
result = manager.perform
|
|
expect(result.action).to eq(:enqueued)
|
|
end
|
|
|
|
it "should not queue posts from admin" do
|
|
manager =
|
|
NewPostManager.new(
|
|
admin,
|
|
raw: "My dog's name is #{require_approval_word.word}.",
|
|
topic_id: topic.id,
|
|
)
|
|
result = manager.perform
|
|
expect(result).to be_success
|
|
expect(result.action).to eq(:create_post)
|
|
end
|
|
|
|
it "should not queue posts from moderator" do
|
|
manager =
|
|
NewPostManager.new(
|
|
moderator,
|
|
raw: "My dog's name is #{require_approval_word.word}.",
|
|
topic_id: topic.id,
|
|
)
|
|
result = manager.perform
|
|
expect(result).to be_success
|
|
expect(result.action).to eq(:create_post)
|
|
end
|
|
|
|
it "doesn't need approval in a private message" do
|
|
manager =
|
|
NewPostManager.new(
|
|
tl2_user,
|
|
raw: "Want some #{require_approval_word.word} for cheap?",
|
|
title: "this is a new title",
|
|
archetype: Archetype.private_message,
|
|
target_usernames: Fabricate(:user, trust_level: TrustLevel[2]).username,
|
|
)
|
|
result = manager.perform
|
|
expect(result).to be_success
|
|
expect(result.action).to eq(:create_post)
|
|
end
|
|
end
|
|
|
|
context "with flag" do
|
|
def should_flag_post(author, raw, topic)
|
|
post = Fabricate(:post, raw: raw, topic: topic, user: author)
|
|
expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to change { PostAction.count }.by(
|
|
1,
|
|
)
|
|
expect(
|
|
PostAction.where(
|
|
post_id: post.id,
|
|
post_action_type_id: PostActionType.types[:inappropriate],
|
|
).exists?,
|
|
).to eq(true)
|
|
end
|
|
|
|
def should_not_flag_post(author, raw, topic)
|
|
post = Fabricate(:post, raw: raw, topic: topic, user: author)
|
|
expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to_not change { PostAction.count }
|
|
end
|
|
|
|
it "should flag the post as inappropriate" do
|
|
topic = Fabricate(:topic, user: tl2_user)
|
|
post = Fabricate(:post, raw: "I said.... #{flag_word.word}", topic: topic, user: tl2_user)
|
|
Jobs::ProcessPost.new.execute(post_id: post.id)
|
|
expect(
|
|
PostAction.where(
|
|
post_id: post.id,
|
|
post_action_type_id: PostActionType.types[:inappropriate],
|
|
).exists?,
|
|
).to eq(true)
|
|
reviewable = ReviewableFlaggedPost.where(target: post)
|
|
expect(reviewable).to be_present
|
|
expect(
|
|
ReviewableScore.where(
|
|
reviewable: reviewable,
|
|
reason: "watched_word",
|
|
context: flag_word.word,
|
|
),
|
|
).to be_present
|
|
end
|
|
|
|
it "should flag the post if it contains multiple flagged words" do
|
|
topic = Fabricate(:topic, user: tl2_user)
|
|
post =
|
|
Fabricate(
|
|
:post,
|
|
raw:
|
|
"I said.... #{flag_word.word} and #{another_flag_word.word} and #{flag_word.word} again",
|
|
topic: topic,
|
|
user: tl2_user,
|
|
)
|
|
expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to change { PostAction.count }.by(
|
|
1,
|
|
)
|
|
expect(
|
|
PostAction.where(
|
|
post_id: post.id,
|
|
post_action_type_id: PostActionType.types[:inappropriate],
|
|
).exists?,
|
|
).to eq(true)
|
|
reviewable = ReviewableFlaggedPost.where(target: post)
|
|
expect(reviewable).to be_present
|
|
expect(
|
|
ReviewableScore.where(
|
|
reviewable: reviewable,
|
|
reason: "watched_word",
|
|
context: "#{flag_word.word},#{another_flag_word.word}",
|
|
),
|
|
).to be_present
|
|
end
|
|
|
|
it "should look at the title too" do
|
|
should_flag_post(
|
|
tl2_user,
|
|
"I thought the movie was not bad actually.",
|
|
Fabricate(:topic, user: tl2_user, title: "Read my #{flag_word.word} review!"),
|
|
)
|
|
end
|
|
|
|
it "shouldn't flag posts by admin" do
|
|
should_not_flag_post(
|
|
admin,
|
|
"I thought the #{flag_word.word} was bad.",
|
|
Fabricate(:topic, user: admin),
|
|
)
|
|
end
|
|
|
|
it "shouldn't flag posts by moderator" do
|
|
should_not_flag_post(
|
|
moderator,
|
|
"I thought the #{flag_word.word} was bad.",
|
|
Fabricate(:topic, user: moderator),
|
|
)
|
|
end
|
|
|
|
it "is compatible with flag_sockpuppets" do
|
|
SiteSetting.flag_sockpuppets = true
|
|
ip_address = "182.189.119.174"
|
|
user1 =
|
|
Fabricate(:user, ip_address: ip_address, created_at: 2.days.ago, refresh_auto_groups: true)
|
|
user2 = Fabricate(:user, ip_address: ip_address, refresh_auto_groups: true)
|
|
first = create_post(user: user1, created_at: 2.days.ago)
|
|
sockpuppet_post =
|
|
create_post(
|
|
user: user2,
|
|
topic: first.topic,
|
|
raw: "I thought the #{flag_word.word} was bad.",
|
|
)
|
|
expect(PostAction.where(post_id: sockpuppet_post.id).count).to eq(1)
|
|
end
|
|
|
|
it "flags in private message too" do
|
|
post =
|
|
Fabricate(
|
|
:private_message_post,
|
|
raw: "Want some #{flag_word.word} for cheap?",
|
|
user: tl2_user,
|
|
)
|
|
expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to change { PostAction.count }.by(
|
|
1,
|
|
)
|
|
expect(
|
|
PostAction.where(
|
|
post_id: post.id,
|
|
post_action_type_id: PostActionType.types[:inappropriate],
|
|
).exists?,
|
|
).to eq(true)
|
|
end
|
|
|
|
it "flags on revisions" do
|
|
Jobs.run_immediately!
|
|
post = Fabricate(:post, topic: Fabricate(:topic, user: tl2_user), user: tl2_user)
|
|
expect {
|
|
PostRevisor.new(post).revise!(
|
|
post.user,
|
|
{ raw: "Want some #{flag_word.word} for cheap?" },
|
|
revised_at: post.updated_at + 10.seconds,
|
|
)
|
|
}.to change { PostAction.count }.by(1)
|
|
expect(
|
|
PostAction.where(
|
|
post_id: post.id,
|
|
post_action_type_id: PostActionType.types[:inappropriate],
|
|
).exists?,
|
|
).to eq(true)
|
|
end
|
|
|
|
it "should not flag on rebake" do
|
|
post =
|
|
Fabricate(
|
|
:post,
|
|
topic: Fabricate(:topic, user: tl2_user),
|
|
user: tl2_user,
|
|
raw: "I have coupon codes. Message me.",
|
|
)
|
|
Fabricate(:watched_word, action: WatchedWord.actions[:flag], word: "coupon")
|
|
expect { post.rebake! }.to_not change { PostAction.count }
|
|
end
|
|
end
|
|
end
|