mirror of
https://github.com/discourse/discourse.git
synced 2026-08-09 21:45:25 +08:00
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"
/>
147 lines
4.6 KiB
Ruby
Vendored
147 lines
4.6 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe ReviewablePost do
|
|
fab!(:admin)
|
|
|
|
describe "#build_actions" do
|
|
let(:post) { Fabricate.build(:post) }
|
|
let(:reviewable) { ReviewablePost.new(target: post, target_created_by: post.user) }
|
|
let(:guardian) { Guardian.new }
|
|
|
|
it "Does not return available actions when the reviewable is no longer pending" do
|
|
available_actions =
|
|
(Reviewable.statuses.keys - ["pending"]).reduce([]) do |actions, status|
|
|
reviewable.status = status
|
|
|
|
actions.concat reviewable_actions(guardian).to_a
|
|
end
|
|
|
|
expect(available_actions).to be_empty
|
|
end
|
|
|
|
it "only shows the approve post action if users cannot delete the post" do
|
|
expect(reviewable_actions(guardian).has?(:approve)).to eq(true)
|
|
expect(reviewable_actions(guardian).has?(:reject_and_delete)).to eq(false)
|
|
end
|
|
|
|
it "includes the reject and delete action if the user is allowed" do
|
|
expect(reviewable_actions(Guardian.new(admin)).has?(:reject_and_delete)).to eq(true)
|
|
end
|
|
|
|
it "includes the approve post and unhide action if the post is hidden" do
|
|
post.hidden = true
|
|
|
|
actions = reviewable_actions(guardian)
|
|
|
|
expect(actions.has?(:approve_and_unhide)).to eq(true)
|
|
end
|
|
|
|
it "includes the reject post and keep deleted action is the post is deleted" do
|
|
post.deleted_at = 1.day.ago
|
|
|
|
actions = reviewable_actions(guardian)
|
|
|
|
expect(actions.has?(:approve_and_restore)).to eq(false)
|
|
expect(actions.has?(:reject_and_keep_deleted)).to eq(true)
|
|
end
|
|
|
|
it "includes an option to approve and restore the post if the user is allowed" do
|
|
post.deleted_at = 1.day.ago
|
|
|
|
actions = reviewable_actions(Guardian.new(admin))
|
|
|
|
expect(actions.has?(:approve_and_restore)).to eq(false)
|
|
end
|
|
|
|
it "doesn't include the suspend action when the author is already suspended" do
|
|
post.user.suspended_till = 1.year.from_now
|
|
post.user.suspended_at = Time.zone.now
|
|
|
|
actions = reviewable_actions(Guardian.new(admin))
|
|
|
|
expect(actions.has?(:reject_and_suspend)).to eq(false)
|
|
expect(actions.has?(:reject_and_silence)).to eq(true)
|
|
end
|
|
|
|
it "doesn't include the silence action when the author is already silenced" do
|
|
post.user.silenced_till = 1.year.from_now
|
|
|
|
actions = reviewable_actions(Guardian.new(admin))
|
|
|
|
expect(actions.has?(:reject_and_suspend)).to eq(true)
|
|
expect(actions.has?(:reject_and_silence)).to eq(false)
|
|
end
|
|
|
|
def reviewable_actions(guardian)
|
|
actions = Reviewable::Actions.new(reviewable, guardian, {})
|
|
reviewable.build_actions(actions, guardian, {})
|
|
|
|
actions
|
|
end
|
|
end
|
|
|
|
describe "Performing actions" do
|
|
let(:post) { Fabricate(:post) }
|
|
let(:reviewable) { ReviewablePost.needs_review!(target: post, created_by: admin) }
|
|
|
|
before { reviewable.created_new! }
|
|
|
|
describe "#perform_approve" do
|
|
it "transitions to the approved state" do
|
|
result = reviewable.perform admin, :approve
|
|
|
|
expect(result.transition_to).to eq :approved
|
|
end
|
|
end
|
|
|
|
describe "#perform_reject_and_suspend" do
|
|
it "transitions to the rejected state" do
|
|
result = reviewable.perform admin, :reject_and_suspend
|
|
|
|
expect(result.transition_to).to eq :rejected
|
|
end
|
|
end
|
|
|
|
describe "#perform_reject_and_keep_deleted" do
|
|
it "transitions to the rejected state and keep the post deleted" do
|
|
post.trash!
|
|
|
|
result = reviewable.perform admin, :reject_and_keep_deleted
|
|
|
|
expect(result.transition_to).to eq :rejected
|
|
expect(Post.where(id: post.id).exists?).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe "#perform_approve_and_restore" do
|
|
it "transitions to the approved state and restores the post" do
|
|
post.trash!
|
|
|
|
result = reviewable.reload.perform admin, :approve_and_restore
|
|
|
|
expect(result.transition_to).to eq :approved
|
|
expect(Post.where(id: post.id).exists?).to eq(true)
|
|
end
|
|
end
|
|
|
|
describe "#perform_approve_and_unhide" do
|
|
it "transitions to the approved state and unhides the post" do
|
|
post.update!(hidden: true)
|
|
|
|
result = reviewable.reload.perform admin, :approve_and_unhide
|
|
|
|
expect(result.transition_to).to eq :approved
|
|
expect(post.reload.hidden).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe "#perform_reject_and_delete" do
|
|
it "transitions to the rejected state and deletes the post" do
|
|
result = reviewable.perform admin, :reject_and_delete
|
|
|
|
expect(result.transition_to).to eq :rejected
|
|
expect(Post.where(id: post.id).exists?).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
end
|