DEV: Centralise user action definitions for reviewables. (#34279)

This change centralises how user actions are defined.

### How It Works

`ReviewableActionBuilder::build_user_actions_bundle` defines all of the
user actions that a reviewable should need.

`ReviewableActionBuilder` also now includes matching `perform_*`
methods, to go with the actions defined in `build_user_actions_bundle`.
There's also an overridable `target_user` method. It defaults to
`target_created_by`, since that's the most common case, but it will
allow different reviewable types to define a different user to target:
for example, `ReviewableUser` would want to have it return `target`.

### Supporting Changes

`Reviewable::build_actions` is temporarily overridden in
`ReviewableActionBuilder`. Because we need `build_actions` to define
both the old and new actions, I've chosen to split these into separate
methods (`build_legacy_combined_actions` and
`build_new_separated_actions`) that reviewables will temporarily need to
define. This allows minimal code churn of the old logic, while we can
build the new logic within its own method. `Reviewable::build_actions`
will ultimately be moved to `ReviewableActionBuilder`.

`Reviewable::create_result` has been copied to
`ReviewableActionBuilder`, and expanded to include the flag-handling
functionality of `ReviewablePost::successful_transition`.
This commit is contained in:
Gary Pendergast
2025-08-26 14:35:53 +10:00
committed by GitHub
parent 8cf83a6104
commit d2516cbdcd
11 changed files with 499 additions and 70 deletions
@@ -3,6 +3,89 @@
module ReviewableActionBuilder
extend ActiveSupport::Concern
# Standard user-actions bundle and default user actions.
#
# @param actions [Reviewable::Actions] Actions instance to add the bundle to.
# @param guardian [Guardian] Guardian instance to check permissions.
#
# @return [Reviewable::Actions::Bundle] The created user actions bundle.
def build_user_actions_bundle(actions, guardian)
bundle =
actions.add_bundle(
"#{id}-user-actions",
label: "reviewables.actions.user_actions.bundle_title",
)
# Always include the no-op action
build_action(actions, :no_action_user, bundle: bundle)
return bundle unless target_user
if guardian.can_silence_user?(target_user)
build_action(actions, :silence_user, bundle: bundle, client_action: "silence")
end
if guardian.can_suspend?(target_user)
build_action(actions, :suspend_user, bundle: bundle, client_action: "suspend")
end
if guardian.can_delete_user?(target_user)
build_action(actions, :delete_user, bundle: bundle)
build_action(actions, :delete_and_block_user, bundle: bundle)
end
bundle
end
# Build actions for the reviewable based on the current state and guardian permissions.
#
# @TODO (reviewable-refresh) Replace this method with {Reviewable#build_actions} once the new UI is fully implemented.
#
# @param actions [Reviewable::Actions] Actions instance to add the bundle to.
# @param guardian [Guardian] Guardian instance to check permissions.
# @param args [Hash] Additional arguments for building actions.
#
# @return [void]
def build_actions(actions, guardian, args)
if guardian.can_see_reviewable_ui_refresh?
build_new_separated_actions(actions, guardian, args)
else
build_legacy_combined_actions(actions, guardian, args)
end
end
# Build legacy combined actions for the reviewable.
#
# Classes that include this module should implement this method to define
# the legacy combined actions for their specific reviewable type.
#
# @TODO (reviewable-refresh) Remove this method once the new UI is fully implemented.
#
# @param actions [Reviewable::Actions] Actions instance to add the bundle to.
# @param guardian [Guardian] Guardian instance to check permissions.
# @param args [Hash] Additional arguments for building actions.
#
# @return [void]
def build_legacy_combined_actions(actions, guardian, args)
raise NotImplementedError, "Including class must implement #build_legacy_combined_actions"
end
# Build new separated actions for the reviewable.
#
# Classes that include this module should implement this method to define
# the new separated actions for their specific reviewable type.
#
# @TODO (reviewable-refresh) Remove this method once the new UI is fully implemented.
#
# @param actions [Reviewable::Actions] Actions instance to add the bundle to.
# @param guardian [Guardian] Guardian instance to check permissions.
# @param args [Hash] Additional arguments for building actions.
#
# @return [void]
def build_new_separated_actions(actions, guardian, args)
raise NotImplementedError, "Including class must implement #build_new_separated_actions"
end
# Build a single reviewable action and add it to the provided actions list.
# This is the canonical API used by both the legacy and refreshed UI code paths.
#
@@ -38,4 +121,93 @@ module ReviewableActionBuilder
action.require_reject_reason = require_reject_reason
end
end
def perform_no_action_user(performed_by, args)
create_result(:success, :approved)
end
def perform_silence_user(performed_by, args)
create_result(:success, :rejected)
end
def perform_suspend_user(performed_by, args)
create_result(:success, :rejected)
end
def perform_delete_user(performed_by, args, &)
delete_user(target_user, delete_opts, performed_by) if target_user
create_result(:success, :rejected, [], recalculate_score: false, &)
end
def perform_delete_and_block_user(performed_by, args, &)
delete_options = delete_opts
delete_options.merge!(block_email: true, block_ip: true) if Rails.env.production?
delete_user(target_user, delete_options, performed_by) if target_user
create_result(:success, :rejected, [], recalculate_score: false, &)
end
private
# Returns the user associated with the reviewable, if applicable.
# For most reviewables, this will be the user who created the reviewable, though some
# reviewables may need to implement this method differently (for example, ReviewableUser).
#
# @return [User] The user associated with the reviewable.
def target_user
try(:target_created_by)
end
# Options for deleting a user, used by perform_delete_user and perform_delete_and_block_user.
def delete_opts
{
delete_posts: true,
prepare_for_destroy: true,
block_urls: true,
delete_as_spammer: true,
context: "review",
}
end
def delete_user(user, delete_options, performed_by)
email = user.email
UserDestroyer.new(performed_by).destroy(user, delete_options)
message = UserNotifications.account_deleted(email, self)
Email::Sender.new(message, :account_deleted).send
end
def map_reviewable_status_to_flag_status(status)
case status
when :approved
:agreed
when :rejected
:disagreed
else
status
end
end
# Create a result object.
#
# @param status [Symbol] The status of the result.
# @param transition_to [Symbol] The state to transition to.
# @param recalculate_score [Boolean] Whether to recalculate the score.
# @yield [result] The result object.
#
# @return [Reviewable::PerformResult] The created result object.
def create_result(status, transition_to = nil, flagging_user_ids = [], recalculate_score = true)
result = Reviewable::PerformResult.new(self, status)
result.transition_to = transition_to
if flagging_user_ids.any?
result.update_flag_stats = {
status: map_reviewable_status_to_flag_status(transition_to),
user_ids: flagging_user_ids,
}
result.recalculate_score = recalculate_score
end
yield result if block_given?
result
end
end
+2
View File
@@ -631,6 +631,7 @@ class Reviewable < ActiveRecord::Base
@@serializers[type] ||= lookup_serializer_for(type)
end
# @TODO (reviewable-refresh) This can be deprecated/removed once all reviewable types have been migrated, it now lives in ReviewableActionBuilder.
def create_result(status, transition_to = nil)
result = PerformResult.new(self, status)
result.transition_to = transition_to
@@ -740,6 +741,7 @@ class Reviewable < ActiveRecord::Base
self.score
end
# TODO (reviewable-refresh) This can be deprecated/removed once all reviewable types have been migrated.
def delete_user_actions(actions, bundle = nil, require_reject_reason: false)
bundle ||=
actions.add_bundle(
+17 -35
View File
@@ -14,6 +14,7 @@ class ReviewableFlaggedPost < Reviewable
agree_and_edit: :agree_and_keep,
disagree_and_restore: :disagree,
ignore_and_do_nothing: :ignore,
delete_user_block: :delete_and_block_user, # legacy name mapped to concern method
}
end
@@ -46,7 +47,12 @@ class ReviewableFlaggedPost < Reviewable
def build_actions(actions, guardian, args)
return unless pending?
return if post.blank?
super
end
# TODO (reviewable-refresh): Remove legacy method once new UI fully deployed
def build_legacy_combined_actions(actions, guardian, args)
# existing combined logic
agree_bundle =
actions.add_bundle("#{id}-agree", icon: "thumbs-up", label: "reviewables.actions.agree.title")
@@ -142,6 +148,11 @@ class ReviewableFlaggedPost < Reviewable
end
end
# TODO (reviewable-refresh): Merge into build_actions post rollout.
def build_new_separated_actions(actions, guardian, args)
build_user_actions_bundle(actions, guardian)
end
def perform_ignore(performed_by, args)
perform_ignore_and_do_nothing(performed_by, args)
end
@@ -173,9 +184,7 @@ class ReviewableFlaggedPost < Reviewable
DiscourseEvent.trigger(:flag_deferred, actions.first)
end
create_result(:success, :ignored) do |result|
result.update_flag_stats = { status: :ignored, user_ids: actions.map(&:user_id) }
end
create_result(:success, :ignored, actions.map(&:user_id), false)
end
def perform_agree_and_keep(performed_by, args)
@@ -183,15 +192,12 @@ class ReviewableFlaggedPost < Reviewable
end
def perform_delete_user(performed_by, args)
delete_user(post.user, delete_opts, performed_by)
super
agree(performed_by, args)
end
def perform_delete_user_block(performed_by, args)
delete_options = delete_opts
delete_options.merge!(block_email: true, block_ip: true) if Rails.env.production?
delete_user(post.user, delete_options, performed_by)
def perform_delete_and_block_user(performed_by, args)
super
agree(performed_by, args)
end
@@ -245,9 +251,7 @@ class ReviewableFlaggedPost < Reviewable
UserSilencer.unsilence(post.user) if UserSilencer.was_silenced_for?(post)
end
create_result(:success, :rejected) do |result|
result.update_flag_stats = { status: :disagreed, user_ids: actions.map(&:user_id) }
end
create_result(:success, :rejected, actions.map(&:user_id), false)
end
def perform_delete_and_ignore(performed_by, args)
@@ -307,10 +311,7 @@ class ReviewableFlaggedPost < Reviewable
yield(actions.first) if block_given?
end
create_result(:success, :approved) do |result|
result.update_flag_stats = { status: :agreed, user_ids: actions.map(&:user_id) }
result.recalculate_score = true
end
create_result(:success, :approved, actions.map(&:user_id), false)
end
def unassign_topic(performed_by, post)
@@ -341,25 +342,6 @@ class ReviewableFlaggedPost < Reviewable
private
def delete_user(user, delete_options, performed_by)
email = user.email
UserDestroyer.new(performed_by).destroy(user, delete_options)
message = UserNotifications.account_deleted(email, self)
Email::Sender.new(message, :account_deleted).send
end
def delete_opts
{
delete_posts: true,
prepare_for_destroy: true,
block_urls: true,
delete_as_spammer: true,
context: "review",
}
end
def destroyer(performed_by, post)
PostDestroyer.new(performed_by, post, reviewable: self)
end
+17 -13
View File
@@ -34,7 +34,11 @@ class ReviewablePost < Reviewable
def build_actions(actions, guardian, args)
return unless pending?
super
end
# TODO (reviewable-refresh): Remove this method when fully migrated to new UI
def build_legacy_combined_actions(actions, guardian, args)
if post.trashed? && guardian.can_recover_post?(post)
build_action(actions, :approve_and_restore, icon: "check")
elsif post.hidden?
@@ -74,48 +78,48 @@ class ReviewablePost < Reviewable
end
end
# TODO (reviewable-refresh): Merge this method into build_actions when fully migrated to new UI
def build_new_separated_actions(actions, guardian, args)
build_user_actions_bundle(actions, guardian)
end
# TODO (reviewable-refresh): Remove combined actions below when fully migrated to new UI
def perform_approve(performed_by, _args)
successful_transition :approved, recalculate_score: false
create_result(:success, :approved, [created_by_id], false)
end
def perform_reject_and_keep_deleted(performed_by, _args)
successful_transition :rejected, recalculate_score: false
create_result(:success, :rejected, [created_by_id], false)
end
def perform_approve_and_restore(performed_by, _args)
PostDestroyer.new(performed_by, post).recover
successful_transition :approved, recalculate_score: false
create_result(:success, :approved, [created_by_id], false)
end
def perform_approve_and_unhide(performed_by, _args)
post.unhide!
successful_transition :approved, recalculate_score: false
create_result(:success, :approved, [created_by_id], false)
end
def perform_reject_and_delete(performed_by, _args)
PostDestroyer.new(performed_by, post, reviewable: self).destroy
successful_transition :rejected, recalculate_score: false
create_result(:success, :rejected, [created_by_id], false)
end
def perform_reject_and_suspend(performed_by, _args)
successful_transition :rejected, recalculate_score: false
create_result(:success, :rejected, [created_by_id], false)
end
# TODO (reviewable-refresh): Remove combined actions above when fully migrated to new UI
private
def post
@post ||= (target || Post.with_deleted.find_by(id: target_id))
end
def successful_transition(to_state, recalculate_score: true)
create_result(:success, to_state) do |result|
result.recalculate_score = recalculate_score
result.update_flag_stats = { status: to_state, user_ids: [created_by_id] }
end
end
end
# == Schema Information
+16 -18
View File
@@ -4,7 +4,7 @@ class ReviewableQueuedPost < Reviewable
include ReviewableActionBuilder
def self.action_aliases
{ discard_post: :reject_post }
{ discard_post: :reject_post, delete_user_block: :delete_and_block_user }
end
after_create do
@@ -38,7 +38,8 @@ class ReviewableQueuedPost < Reviewable
reviewable_scores.pending.or(reviewable_scores.disagreed)
end
def build_actions(actions, guardian, args)
# TODO (reviewable-refresh): Remove this method once new UI is fully deployed
def build_legacy_combined_actions(actions, guardian, args)
unless approved?
if topic&.closed?
build_action(actions, :approve_post_closed, icon: "check", confirm: true)
@@ -70,6 +71,10 @@ class ReviewableQueuedPost < Reviewable
build_action(actions, :delete) if guardian.can_delete?(self)
end
def build_new_separated_actions(actions, guardian, args)
build_user_actions_bundle(actions, guardian) if pending?
end
def build_editable_fields(fields, guardian, args)
if pending?
# We can edit category / title if it's a new topic
@@ -185,28 +190,21 @@ class ReviewableQueuedPost < Reviewable
end
def perform_delete_user(performed_by, args)
delete_user(performed_by, delete_opts)
reviewable_ids = Reviewable.where(created_by: target_created_by).pluck(:id)
result = super { |r| r.remove_reviewable_ids += reviewable_ids }
update_column(:target_created_by_id, nil)
result
end
def perform_delete_user_block(performed_by, args)
delete_options = delete_opts
delete_options.merge!(block_email: true, block_ip: true) if Rails.env.production?
delete_user(performed_by, delete_options)
def perform_delete_and_block_user(performed_by, args)
reviewable_ids = Reviewable.where(created_by: target_created_by).pluck(:id)
result = super { |r| r.remove_reviewable_ids += reviewable_ids }
update_column(:target_created_by_id, nil)
result
end
private
def delete_user(performed_by, delete_options)
reviewable_ids = Reviewable.where(created_by: target_created_by).pluck(:id)
UserDestroyer.new(performed_by).destroy(target_created_by, delete_options)
update_column(:target_created_by_id, nil)
create_result(:success, :rejected) { |r| r.remove_reviewable_ids += reviewable_ids }
end
def delete_opts
{
context: I18n.t("reviewables.actions.delete_user.reason"),
+7
View File
@@ -13,12 +13,19 @@ class ReviewableUser < Reviewable
def build_actions(actions, guardian, args)
return unless pending?
super
end
def build_legacy_combined_actions(actions, guardian, args)
build_action(actions, :approve_user, icon: "user-plus") if guardian.can_approve?(target)
delete_user_actions(actions, require_reject_reason: !is_a_suspect_user?)
end
def build_new_separated_actions(actions, guardian, args)
build_legacy_combined_actions(actions, guardian, args)
end
def perform_approve_user(performed_by, args)
ReviewableUser.set_approved_fields!(target, performed_by)
target.save!
+24 -1
View File
@@ -5844,9 +5844,32 @@ en:
approve_and_unhide:
title: "Approve and Unhide post"
complete: "Post approved and unhidden"
# New separated user actions
user_actions:
bundle_title: "What do you want to do with the user?"
no_action_user:
title: "No action"
description: "Take no action against the user"
complete: "No action taken against user"
silence_user:
title: "Silence user"
description: "Prevent the user from posting"
complete: "User silenced"
reason: "User silenced via review queue"
suspend_user:
title: "Suspend user"
description: "Suspend the user account"
complete: "User suspended"
reason: "User suspended via review queue"
delete_user:
title: "Delete user"
description: "Delete this user from the forum"
complete: "User deleted"
reason: "Deleted via review queue"
complete: "User deleted."
delete_and_block_user:
title: "Delete & block"
description: "Delete the user and block their IP/email"
complete: "User deleted and blocked"
email_style:
html_missing_placeholder: "The html template must include %{placeholder}"
@@ -5,6 +5,46 @@ RSpec.describe ReviewableActionBuilder do
fab!(:guardian) { Guardian.new(admin) }
fab!(:user)
describe "#build_user_actions_bundle" do
fab!(:post) { Fabricate(:post, user: user) }
fab!(:reviewable_post) do
ReviewablePost.needs_review!(target: post, created_by: admin, potential_spam: false)
end
fab!(:post_actions) { Reviewable::Actions.new(reviewable_post, guardian) }
it "creates a user bundle with standard actions when allowed" do
bundle = reviewable_post.build_user_actions_bundle(post_actions, guardian)
# bundle id and label
expect(bundle.id).to eq("#{reviewable_post.id}-user-actions")
expect(bundle.label).to eq("reviewables.actions.user_actions.bundle_title")
# action ids are prefixed with target type (post-...)
action_ids = bundle.actions.map(&:id)
expect(action_ids).to include("post-no_action_user")
expect(action_ids).to include("post-silence_user")
expect(action_ids).to include("post-suspend_user")
expect(action_ids).to include("post-delete_user")
expect(action_ids).to include("post-delete_and_block_user")
# client_action is set for moderation actions
silence = bundle.actions.find { |a| a.id == "post-silence_user" }
suspend = bundle.actions.find { |a| a.id == "post-suspend_user" }
expect(silence.client_action).to eq("silence")
expect(suspend.client_action).to eq("suspend")
end
it "includes only the no-op action when user is nil" do
allow(reviewable_post).to receive(:target_created_by).and_return(nil)
bundle = reviewable_post.build_user_actions_bundle(post_actions, guardian)
server_actions = bundle.actions.map(&:server_action)
expect(server_actions).to include("no_action_user")
expect(server_actions - ["no_action_user"]).to be_empty
end
end
describe "#build_action" do
fab!(:reviewable_user) { ReviewableUser.create_for(user) }
@@ -313,6 +313,28 @@ RSpec.describe ReviewableFlaggedPost, type: :model do
expect(post.user_deleted?).to eq(false)
expect(post.hidden?).to eq(false)
end
context "when reviewable_ui_refresh enabled (separated bundles)" do
before do
# Stub guardian check on reviewable to simulate feature flag on
allow_any_instance_of(Guardian).to receive(:can_see_reviewable_ui_refresh?).and_return(true)
end
it "builds user actions bundle with moderation actions" do
actions = reviewable.actions_for(guardian)
user_bundle = actions.bundles.find { |b| b.id.ends_with?("-user-actions") }
expect(user_bundle).to be_present
expect(actions.has?(:silence_user)).to eq(true)
expect(actions.has?(:suspend_user)).to eq(true)
expect(actions.has?(:delete_user)).to eq(true)
end
it "omits user deletion when reviewer cannot delete user" do
allow(guardian).to receive(:can_delete_user?).and_return(false)
actions = reviewable.actions_for(guardian)
expect(actions.has?(:delete_user)).to eq(false)
expect(actions.has?(:delete_and_block_user)).to eq(false)
end
end
end
describe "pending count" do
+93
View File
@@ -4,6 +4,7 @@ RSpec.describe ReviewablePost do
fab!(:admin)
describe "#build_actions" do
# TODO (reviewable-refresh): Remove the tests below when the legacy combined actions are removed
let(:post) { Fabricate.build(:post) }
let(:reviewable) { ReviewablePost.new(target: post) }
let(:guardian) { Guardian.new }
@@ -59,6 +60,66 @@ RSpec.describe ReviewablePost do
actions
end
# TODO (reviewable-refresh): Remove the tests above when the legacy combined actions are removed
context "with new UI (separated post and user actions)" do
let(:post) { Fabricate.build(:post) }
let(:reviewable) { ReviewablePost.new(target: post, target_created_by: post.user) }
let(:guardian) { Guardian.new }
let(:admin_guardian) { Guardian.new(admin) }
before do
allow_any_instance_of(Guardian).to receive(:can_see_reviewable_ui_refresh?).and_return(true)
end
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 "includes user actions when target_created_by is present" do
actions = reviewable_actions(guardian)
expect(actions.has?(:no_action_user)).to eq(true)
end
it "includes suspend and silence actions for admins" do
actions = reviewable_actions(admin_guardian)
expect(actions.has?(:suspend_user)).to eq(true)
expect(actions.has?(:silence_user)).to eq(true)
end
it "includes delete user actions for admins" do
actions = reviewable_actions(admin_guardian)
expect(actions.has?(:delete_user)).to eq(true)
expect(actions.has?(:delete_and_block_user)).to eq(true)
end
it "includes a minimal user actions bundle when no target_created_by" do
reviewable.target_created_by = nil
actions = reviewable_actions(guardian)
expect(actions.has?(:no_action_user)).to eq(true)
expect(actions.has?(:silence_user)).to eq(false)
expect(actions.has?(:suspend_user)).to eq(false)
expect(actions.has?(:delete_user)).to eq(false)
expect(actions.has?(:delete_and_block_user)).to eq(false)
end
def reviewable_actions(guardian)
actions = Reviewable::Actions.new(reviewable, guardian, {})
reviewable.build_actions(actions, guardian, {})
actions
end
end
end
describe "Performing actions" do
@@ -67,6 +128,7 @@ RSpec.describe ReviewablePost do
before { reviewable.created_new! }
# TODO (reviewable-refresh): Remove the tests below when the legacy combined actions are removed
describe "#perform_approve" do
it "transitions to the approved state" do
result = reviewable.perform admin, :approve
@@ -124,5 +186,36 @@ RSpec.describe ReviewablePost do
expect(Post.where(id: post.id).exists?).to eq(false)
end
end
# TODO (reviewable-refresh): Remove the tests above when the legacy combined actions are removed
context "with new separated actions" do
before do
allow_any_instance_of(Guardian).to receive(:can_see_reviewable_ui_refresh?).and_return(true)
end
describe "#perform_silence_user" do
it "transitions to rejected" do
result = reviewable.perform admin, :silence_user
expect(result.transition_to).to eq :rejected
end
end
describe "#perform_suspend_user" do
it "transitions to rejected" do
result = reviewable.perform admin, :suspend_user
expect(result.transition_to).to eq :rejected
end
end
describe "#perform_no_action_user" do
it "transitions to approved" do
result = reviewable.perform admin, :no_action_user
expect(result.transition_to).to eq :approved
end
end
end
end
end
+89 -3
View File
@@ -296,8 +296,7 @@ RSpec.describe ReviewableQueuedPost, type: :model do
it "creates a topic with staff tag when approved" do
hidden_tag = Fabricate(:tag)
staff_tag_group =
Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [hidden_tag.name])
Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [hidden_tag.name])
reviewable.payload["tags"] += [hidden_tag.name]
result = reviewable.perform(moderator, :approve_post)
@@ -322,7 +321,7 @@ RSpec.describe ReviewableQueuedPost, type: :model do
end
it "remaps tags with synonyms when approved" do
syn_tag = Fabricate(:tag, name: "syntag", target_tag: Fabricate(:tag, name: "maintag"))
Fabricate(:tag, name: "syntag", target_tag: Fabricate(:tag, name: "maintag"))
reviewable.payload["tags"] += ["syntag"]
result = reviewable.perform(moderator, :approve_post)
@@ -370,4 +369,91 @@ RSpec.describe ReviewableQueuedPost, type: :model do
end
end
end
describe "separated actions UI" do
fab!(:admin)
fab!(:user)
let(:reviewable) { Fabricate(:reviewable_queued_post, target_created_by: user) }
context "when reviewable_ui_refresh feature is enabled" do
before do
allow_any_instance_of(Guardian).to receive(:can_see_reviewable_ui_refresh?).and_return(true)
end
it "includes user actions in the user bundle" do
actions = reviewable.actions_for(Guardian.new(admin))
expect(actions.has?(:no_action_user)).to eq(true)
expect(actions.has?(:silence_user)).to eq(true)
expect(actions.has?(:suspend_user)).to eq(true)
expect(actions.has?(:delete_user)).to eq(true)
expect(actions.has?(:delete_and_block_user)).to eq(true)
end
it "includes a minimal user bundle when target_created_by is nil" do
reviewable.update!(target_created_by: nil)
actions = reviewable.actions_for(Guardian.new(admin))
expect(actions.has?(:no_action_user)).to eq(true)
expect(actions.has?(:silence_user)).to eq(false)
expect(actions.has?(:suspend_user)).to eq(false)
expect(actions.has?(:delete_user)).to eq(false)
expect(actions.has?(:delete_and_block_user)).to eq(false)
end
describe "perform methods" do
it "performs no_action_user successfully" do
result = reviewable.perform(admin, :no_action_user)
expect(result.success?).to eq(true)
end
it "performs silence_user successfully" do
expect(user.silenced?).to eq(false)
result = reviewable.perform(admin, :silence_user)
expect(result.success?).to eq(true)
end
it "performs suspend_user successfully" do
expect(user.suspended?).to eq(false)
result = reviewable.perform(admin, :suspend_user)
expect(result.success?).to eq(true)
end
it "performs delete_and_block_user successfully" do
result = reviewable.perform(admin, :delete_and_block_user)
expect(result.success?).to eq(true)
expect(User.find_by(id: user.id)).to be_nil
end
end
end
# TODO (reviewable-refresh): Remove the tests below when the legacy combined actions are removed
context "when reviewable_ui_refresh feature is disabled" do
before do
allow_any_instance_of(Guardian).to receive(:can_see_reviewable_ui_refresh?).and_return(
false,
)
end
it "uses legacy bundle structure" do
actions = reviewable.actions_for(Guardian.new(admin))
bundle_ids = actions.bundles.map(&:id)
expect(bundle_ids).to include("#{reviewable.id}-reject")
expect(bundle_ids).not_to include("#{reviewable.id}-post-actions")
expect(bundle_ids).not_to include("#{reviewable.id}-user-actions")
end
it "includes legacy actions" do
actions = reviewable.actions_for(Guardian.new(admin))
action_ids = actions.to_a.map(&:id).map(&:to_s)
expect(action_ids).to include("approve_post")
expect(action_ids).to include("discard_post")
expect(action_ids).to include("revise_and_reject_post")
expect(action_ids).to include("delete_user")
expect(action_ids).to include("delete_user_block")
end
end
end
end