mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
- Adds an activation filter (All / Activated / Not activated) to the Admin > Users > New tab so unverified accounts can be isolated in the UI. - Adds a bulk suspend action next to the existing bulk delete, plus a "Select all" / "Clear all" control so a whole page of accounts can be actioned at once. - Shows the suspend reason on the Suspended tab, mirroring the silenced tab. Already-suspended users are excluded from bulk suspension, and the penalty-reason columns are populated from a single batched query instead of one query per row. **SCREENSHOTS** <img width="1400" height="1200" alt="desktop-horizon-light-admin-users-activation-filter" src="https://github.com/user-attachments/assets/31165d10-42cb-479b-897c-fafeb3283526" /> <img width="1400" height="1200" alt="desktop-horizon-light-admin-users-bulk-select" src="https://github.com/user-attachments/assets/037e8149-2ac6-40de-aaae-0d0e443ec143" /> <img width="1400" height="1200" alt="desktop-horizon-light-admin-users-suspended" src="https://github.com/user-attachments/assets/e6dbefed-4aaf-49bb-a9bd-52c7253e14b6" /> <img width="1400" height="1200" alt="desktop-horizon-light-admin-users-bulk-suspend" src="https://github.com/user-attachments/assets/5bdf85ef-4e30-4d8d-b521-d8639c1a2b97" />
77 lines
2.4 KiB
Ruby
Vendored
77 lines
2.4 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe User::BulkSuspend do
|
|
describe described_class::Contract, type: :model do
|
|
it { is_expected.to validate_length_of(:user_ids).as_array.is_at_most(100).is_at_least(1) }
|
|
it { is_expected.to validate_presence_of(:reason) }
|
|
it { is_expected.to validate_length_of(:reason).is_at_most(300) }
|
|
it { is_expected.to validate_presence_of(:suspend_until) }
|
|
end
|
|
|
|
describe ".call" do
|
|
subject(:result) { described_class.call(params:, **dependencies) }
|
|
|
|
fab!(:admin)
|
|
fab!(:users) { Fabricate.times(5, :user) }
|
|
|
|
let(:params) { { user_ids:, reason: "spam wave", suspend_until: 1.year.from_now } }
|
|
let(:dependencies) { { guardian: } }
|
|
let(:guardian) { admin.guardian }
|
|
let(:user_ids) { users.map(&:id) }
|
|
|
|
context "when invalid data is provided" do
|
|
let(:user_ids) { nil }
|
|
|
|
it { is_expected.to fail_a_contract }
|
|
end
|
|
|
|
context "when no reason is provided" do
|
|
let(:params) { { user_ids:, suspend_until: 1.year.from_now } }
|
|
|
|
it { is_expected.to fail_a_contract }
|
|
end
|
|
|
|
context "when provided users does not exist" do
|
|
let(:user_ids) { 0 }
|
|
|
|
it { is_expected.to fail_to_find_a_model(:users) }
|
|
end
|
|
|
|
context "when at least one user cannot be suspended" do
|
|
before { users << Fabricate(:admin) }
|
|
|
|
it { is_expected.to fail_a_policy(:can_suspend_users) }
|
|
end
|
|
|
|
context "when at least one user is already suspended" do
|
|
before { users.first.update!(suspended_till: 1.day.from_now, suspended_at: Time.zone.now) }
|
|
|
|
it { is_expected.to fail_a_policy(:can_suspend_users) }
|
|
end
|
|
|
|
context "when everything's ok" do
|
|
before { allow(MessageBus).to receive(:publish) }
|
|
|
|
it "suspends each user" do
|
|
result
|
|
expect(User.where(id: user_ids).where.not(suspended_till: nil).count).to eq(users.size)
|
|
end
|
|
|
|
it "logs the suspension with its reason" do
|
|
result
|
|
histories =
|
|
UserHistory.where(action: UserHistory.actions[:suspend_user], target_user_id: user_ids)
|
|
expect(histories.count).to eq(users.size)
|
|
expect(histories.pluck(:details).uniq).to eq(["spam wave"])
|
|
end
|
|
|
|
it "publishes suspension progress" do
|
|
result
|
|
expect(MessageBus).to have_received(:publish)
|
|
.with("/bulk-user-suspend", a_kind_of(Hash), user_ids: [admin.id])
|
|
.exactly(user_ids.size)
|
|
.times
|
|
end
|
|
end
|
|
end
|
|
end
|