0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 13:08:40 +08:00
discourse/spec/fabricators/reviewable_fabricator.rb
Régis Hanol ca469579dc
FEATURE: Allow suspending and silencing users from the review queue (#42205)
Previously, suspect-user reviewables ("user needs approval" flags) and
queued-post reviewables could only be resolved by deleting the user or
rejecting the post outright — a reviewer mistake on an automated false
positive was irreversible, and sites with no-deletion policies had to
resolve the flag and then manually suspend the user from their admin
page. Requested in [meta t/408891](https://meta.discourse.org/t/408891)
and [meta t/225660](https://meta.discourse.org/t/225660).

This change adds guardian-gated **Silence user** / **Suspend user**
resolutions to both queues via a shared `build_penalty_actions` helper,
using the same penalize-modal flow the flagged-post and
review-every-post queues already use (staff log linked back to the
reviewable). Along the way it:

- hides penalty actions that are already active everywhere (previously a
resolve-then-409 dead end), and fixes the penalize modal's unawaited
`before()` race so the penalty is only applied once the reviewable
action succeeded;
- narrows the rejected-user **scrub** affordance to records whose
identity snapshot is the last remaining copy (user deleted, or
renamed/anonymized after a failed deletion), admin-gated to match the
endpoint;
- shows an active penalty (localized end date and reason) on the
reviewable user card, so it's clear why a penalty option is absent;
- preloads `anonymous_user_master` on the queue since the new
`silenced?` gates would otherwise lazy-load it per row;
- rewrites the action descriptions in one consistent voice.

<img width="460" height="313" alt="2026-07-31 @ 07 46 57"
src="https://github.com/user-attachments/assets/87d5bf49-97e0-47a4-939e-16da3f55c6e1"
/>
2026-07-31 14:47:40 +02:00

135 lines
3.2 KiB
Ruby
Vendored

# frozen_string_literal: true
require "faker"
Fabricator(:reviewable) do
reviewable_by_moderator true
type "ReviewableUser"
created_by { Fabricate(:user) }
target_id { Fabricate(:user).id }
target_type "User"
target_created_by { Fabricate(:user) }
category
score 1.23
payload { { list: [1, 2, 3], name: "bandersnatch" } }
status { :pending }
end
Fabricator(:reviewable_queued_post_topic, class_name: :reviewable_queued_post) do
reviewable_by_moderator true
type "ReviewableQueuedPost"
created_by { Fabricate(:user) }
target_created_by { Fabricate(:user) }
category
payload do
{
raw: "hello world post contents.",
title: "queued post title",
tags: %w[cool neat],
extra: "some extra data",
archetype: "regular",
}
end
end
Fabricator(:suspect_user_reviewable, class_name: :reviewable_user) do
reviewable_by_moderator true
created_by { Discourse.system_user }
target { Fabricate(:user, approved: false) }
payload do |attrs|
{
"username" => attrs[:target].username,
"name" => attrs[:target].name,
"email" => attrs[:target].email,
}
end
status { :pending }
after_create do |reviewable|
reviewable.add_score(
Discourse.system_user,
ReviewableScore.types[:needs_approval],
reason: :suspect_user,
force_review: true,
)
end
end
Fabricator(:reviewable_queued_post) do
reviewable_by_moderator true
type "ReviewableQueuedPost"
created_by { Fabricate(:user) }
target_created_by { Fabricate(:user) }
topic
payload do
{
raw: "hello world post contents.",
reply_to_post_number: 1,
via_email: true,
raw_email: "store_me",
auto_track: true,
custom_fields: {
hello: "world",
},
cooking_options: {
cat: "hat",
},
cook_method: Post.cook_methods[:raw_html],
image_sizes: {
"http://foo.bar/image.png" => {
"width" => 0,
"height" => 222,
},
},
}
end
end
Fabricator(:reviewable_queued_long_post, from: :reviewable_queued_post) do
reviewable_by_moderator true
type "ReviewableQueuedPost"
created_by { Fabricate(:user) }
target_created_by { Fabricate(:user) }
topic
payload do
{
raw: Faker::DiscourseMarkdown.sandwich(sentences: 6, repeat: 3),
reply_to_post_number: 1,
via_email: true,
raw_email: "store_me",
auto_track: true,
custom_fields: {
hello: "world",
},
cooking_options: {
cat: "hat",
},
cook_method: Post.cook_methods[:raw_html],
image_sizes: {
"http://foo.bar/image.png" => {
"width" => 0,
"height" => 222,
},
},
}
end
end
Fabricator(:reviewable_flagged_post) do
reviewable_by_moderator true
type "ReviewableFlaggedPost"
created_by { Fabricate(:user) }
target_created_by { Fabricate(:user) }
topic
target_type "Post"
target { |attrs| Fabricate(:post, topic: attrs[:topic]) }
reviewable_scores { |attrs| [Fabricate.build(:reviewable_score, reviewable_id: attrs[:id])] }
end
Fabricator(:reviewable_user) do
reviewable_by_moderator true
type "ReviewableUser"
created_by { Fabricate(:user) }
target_type "User"
target { Fabricate(:user) }
end