0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 06:24:48 +08:00
discourse/spec/system/admin_user_spec.rb
Arpit Jalan 8db91a42d8
FIX: Restore enabled checkboxes for similar users in penalty modals (#41801)
The suspend user modal lists other accounts sharing the same IP address,
with a checkbox next to each so staff can penalize them in the same
action. Since #41227, every one of those checkboxes was disabled, making
it impossible to select anyone.

The modal enables a checkbox only when the user's `can_be_suspended`
attribute is `true` in the similar-users response. That attribute
regressed as a side effect of #41227: it added `can_be_suspended` to the
parent `AdminUserListSerializer` behind an `include_can_be_suspended?`
opt-in guard for the admin users list. `SimilarAdminUserSerializer`
inherited that guard, and since the similar-users endpoint never passes
the opt-in option, the attribute was silently dropped from its response
— and a missing attribute reads as "cannot be suspended", disabling the
checkbox.

This change overrides the guard in `SimilarAdminUserSerializer` so the
attribute is always serialized, as it was before. It also adds a request
spec pinning `can_be_suspended`/`can_be_silenced` in the response, and
extends the suspend system spec to tick a similar user's checkbox and
verify both accounts get suspended — closing the coverage gap that let
this regress unnoticed.
2026-07-17 17:14:39 +05:30

402 lines
14 KiB
Ruby
Vendored

# frozen_string_literal: true
describe "Admin User Page" do
fab!(:current_user, :admin)
let(:admin_users_page) { PageObjects::Pages::AdminUsers.new }
let(:admin_user_page) { PageObjects::Pages::AdminUser.new }
let(:suspend_user_modal) { PageObjects::Modals::PenalizeUser.new("suspend") }
let(:silence_user_modal) { PageObjects::Modals::PenalizeUser.new("silence") }
before { sign_in(current_user) }
context "when visiting an admin's page" do
fab!(:admin)
before { admin_user_page.visit(admin) }
it "doesn't display the suspend or silence buttons" do
expect(admin_user_page).to have_no_suspend_button
expect(admin_user_page).to have_no_silence_button
end
end
context "when visiting a moderator's page" do
fab!(:moderator)
before { admin_user_page.visit(moderator) }
it "doesn't display the suspend or silence buttons" do
expect(admin_user_page).to have_no_suspend_button
expect(admin_user_page).to have_no_silence_button
end
end
context "when approving a user with a previously rejected reviewable" do
before { SiteSetting.must_approve_users = true }
it "approves the user from the admin profile page" do
stuck_user = Fabricate(:user, active: true, approved: false)
Fabricate(:reviewable_user, target: stuck_user, status: Reviewable.statuses[:rejected])
admin_user_page.visit(stuck_user)
admin_user_page.click_approve_button
expect(admin_user_page).to have_approve_success
expect(stuck_user.reload).to be_approved
end
end
context "when visiting a regular user's page" do
fab!(:user) { Fabricate(:user, ip_address: "93.123.44.90") }
fab!(:similar_user) { Fabricate(:user, ip_address: user.ip_address) }
fab!(:another_mod) { Fabricate(:moderator, ip_address: user.ip_address) }
fab!(:another_admin) { Fabricate(:admin, ip_address: user.ip_address) }
before { admin_user_page.visit(user) }
it "can list accounts with identical IPs" do
find(".ip-lookup-trigger").click
expect(page).to have_content("#{I18n.t("js.ip_lookup.other_accounts")}\n3")
table = page.find(".other-accounts table")
expect(table).to have_content(similar_user.username)
expect(table).to have_content(another_mod.username)
expect(table).to have_content(another_admin.username)
end
it "displays the suspend and silence buttons" do
expect(admin_user_page).to have_suspend_button
expect(admin_user_page).to have_silence_button
end
it "displays username in the title" do
expect(page).to have_css(".display-row.username")
expect(page.title).to eq("#{user.username} - Users - Admin - Discourse")
end
describe "the upcoming changes section" do
fab!(:group1) { Fabricate(:group, name: "test_group_1") }
fab!(:group2) { Fabricate(:group, name: "test_group_2") }
before do
mock_upcoming_change_metadata(
{
enable_upload_debug_mode: {
impact: "feature,all_members",
status: :beta,
impact_type: "feature",
impact_role: "all_members",
},
},
)
end
def open_upcoming_changes
admin_user_page.open_upcoming_changes_modal
end
context "when the change is enabled for everyone" do
before { SiteSetting.enable_upload_debug_mode = true }
it "displays the upcoming change with enabled status and correct reason" do
admin_user_page.visit(user)
open_upcoming_changes
expect(admin_user_page).to have_upcoming_change("enable_upload_debug_mode")
expect(admin_user_page.upcoming_change("enable_upload_debug_mode")).to be_enabled
expect(admin_user_page.upcoming_change("enable_upload_debug_mode")).to have_reason(
"enabled_for_everyone",
)
expect(
admin_user_page.upcoming_change("enable_upload_debug_mode"),
).to have_no_specific_groups
end
end
context "when the change is disabled for everyone" do
before { SiteSetting.enable_upload_debug_mode = false }
it "displays the upcoming change with disabled status and correct reason" do
admin_user_page.visit(user)
open_upcoming_changes
expect(admin_user_page).to have_upcoming_change("enable_upload_debug_mode")
expect(admin_user_page.upcoming_change("enable_upload_debug_mode")).to be_disabled
expect(admin_user_page.upcoming_change("enable_upload_debug_mode")).to have_reason(
"enabled_for_no_one",
)
expect(
admin_user_page.upcoming_change("enable_upload_debug_mode"),
).to have_no_specific_groups
end
end
context "when the change is enabled for specific groups" do
before do
SiteSetting.enable_upload_debug_mode = true
Fabricate(
:site_setting_group,
name: "enable_upload_debug_mode",
group_ids: "#{group1.id}|#{group2.id}",
)
end
context "when the user belongs to one of those groups" do
before { group1.add(user) }
it "displays the upcoming change with enabled status, correct reason, and specific groups" do
admin_user_page.visit(user)
open_upcoming_changes
expect(admin_user_page).to have_upcoming_change("enable_upload_debug_mode")
expect(admin_user_page.upcoming_change("enable_upload_debug_mode")).to be_enabled
expect(admin_user_page.upcoming_change("enable_upload_debug_mode")).to have_reason(
"in_specific_groups",
)
expect(
admin_user_page.upcoming_change("enable_upload_debug_mode"),
).to have_specific_groups(["test_group_1"])
end
end
context "when the user belongs to multiple groups" do
before do
group1.add(user)
group2.add(user)
end
it "displays the upcoming change with all groups" do
admin_user_page.visit(user)
open_upcoming_changes
expect(admin_user_page).to have_upcoming_change("enable_upload_debug_mode")
expect(admin_user_page.upcoming_change("enable_upload_debug_mode")).to be_enabled
expect(admin_user_page.upcoming_change("enable_upload_debug_mode")).to have_reason(
"in_specific_groups",
)
expect(
admin_user_page.upcoming_change("enable_upload_debug_mode"),
).to have_specific_groups(%w[test_group_1 test_group_2])
end
end
context "when the user does not belong to any of those groups" do
it "displays the upcoming change with disabled status, correct reason, and no specific groups" do
admin_user_page.visit(user)
open_upcoming_changes
expect(admin_user_page).to have_upcoming_change("enable_upload_debug_mode")
expect(admin_user_page.upcoming_change("enable_upload_debug_mode")).to be_disabled
expect(admin_user_page.upcoming_change("enable_upload_debug_mode")).to have_reason(
"not_in_specific_groups",
)
expect(
admin_user_page.upcoming_change("enable_upload_debug_mode"),
).to have_no_specific_groups
end
end
end
it "does not show conceptual upcoming changes" do
mock_upcoming_change_metadata(
{
enable_upload_debug_mode: {
impact: "feature,all_members",
status: :beta,
impact_type: "feature",
impact_role: "all_members",
},
about_page_extra_groups_show_description: {
impact: "feature,all_members",
status: :conceptual,
impact_type: "feature",
impact_role: "all_members",
},
},
)
admin_user_page.visit(user)
open_upcoming_changes
expect(admin_user_page).to have_upcoming_change("enable_upload_debug_mode")
expect(admin_user_page).to have_no_upcoming_change(
"about_page_extra_groups_show_description",
)
end
end
describe "the suspend user modal" do
it "displays the list of users who share the same IP but are not mods or admins" do
admin_user_page.click_suspend_button
expect(suspend_user_modal.similar_users).to contain_exactly(similar_user.username)
expect(admin_user_page.similar_users_warning).to include(
I18n.t("admin_js.admin.user.other_matches", count: 1, username: user.username),
)
end
it "suspends the user along with selected similar users and unsuspends the user" do
admin_user_page.click_suspend_button
suspend_user_modal.select_similar_user(similar_user.username)
suspend_user_modal.fill_in_suspend_reason("spamming")
suspend_user_modal.set_future_date("tomorrow")
suspend_user_modal.perform
expect(suspend_user_modal).to be_closed
expect(page).to have_css(".suspension-info")
expect(similar_user.reload).to be_suspended
admin_user_page.click_unsuspend_button
expect(page).not_to have_css(".suspension-info")
end
it "displays error when used is already suspended" do
admin_user_page.click_suspend_button
suspend_user_modal.fill_in_suspend_reason("spamming")
suspend_user_modal.set_future_date("tomorrow")
user.update!(suspended_till: 1.day.from_now)
StaffActionLogger.new(current_user).log_user_suspend(user, "spamming")
suspend_user_modal.perform
expect(suspend_user_modal).to have_error_message(
"User was already suspended by #{current_user.username} just now.",
)
expect(suspend_user_modal).to be_open
end
end
describe "the silence user modal" do
it "displays the list of users who share the same IP but are not mods or admins" do
admin_user_page.click_silence_button
expect(silence_user_modal.similar_users).to contain_exactly(similar_user.username)
expect(admin_user_page.similar_users_warning).to include(
I18n.t("admin_js.admin.user.other_matches", count: 1, username: user.username),
)
end
it "silence and unsilence the user" do
admin_user_page.click_silence_button
silence_user_modal.fill_in_silence_reason("spamming")
silence_user_modal.set_future_date("tomorrow")
silence_user_modal.perform
expect(silence_user_modal).to be_closed
expect(page).to have_css(".silence-info")
admin_user_page.click_unsilence_button
expect(page).not_to have_css(".silence-info")
end
end
end
describe "custom groups" do
fab!(:user)
fab!(:group)
it "saves and displays the added group" do
admin_user_page.visit(user)
group_chooser = admin_user_page.custom_groups_chooser
group_chooser.expand
group_chooser.select_row_by_value(group.id)
expect(admin_user_page).to have_custom_groups_save_button
admin_user_page.save_custom_groups
expect(admin_user_page).to have_no_custom_groups_save_button
expect(admin_user_page).to have_custom_group(group.name)
expect(GroupUser.exists?(user:, group:)).to eq(true)
end
it "saves and removes the group from the list" do
group.add(user)
admin_user_page.visit(user)
expect(admin_user_page).to have_custom_group(group.name)
group_chooser = admin_user_page.custom_groups_chooser
group_chooser.expand
group_chooser.unselect_by_name(group.name)
expect(admin_user_page).to have_custom_groups_save_button
admin_user_page.save_custom_groups
expect(admin_user_page).to have_no_custom_groups_save_button
expect(admin_user_page).to have_no_custom_group(group.name)
expect(GroupUser.exists?(user:, group:)).to eq(false)
end
end
context "when logged in as a moderator" do
fab!(:current_user, :moderator)
context "when visiting a regular user's page" do
fab!(:user)
context "when moderators_change_trust_levels setting is enabled" do
before { SiteSetting.moderators_change_trust_levels = true }
it "the dropdown to change trust level is enabled" do
admin_user_page.visit(user)
expect(admin_user_page).to have_change_trust_level_dropdown_enabled
end
end
context "when moderators_change_trust_levels setting is disabled" do
before { SiteSetting.moderators_change_trust_levels = false }
it "the dropdown to change trust level is disabled" do
admin_user_page.visit(user)
expect(admin_user_page).to have_change_trust_level_dropdown_disabled
end
end
end
end
context "when navigating to a user's page from the list" do
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
it "displays the groups correctly" do
admin_users_page.visit
# navigate to the user page
admin_users_page.user_row(user.id).username.click
# ensure the automatic groups are displayed
page.find(".admin-user__automatic-groups").has_text?("trust_level")
end
describe "with user action logs" do
let(:staff_action_logs_page) { PageObjects::Pages::AdminStaffActionLogs.new }
fab!(:user_a, :user)
fab!(:user_a_silenced) do
Fabricate(:user_history, action: UserHistory.actions[:silence_user], target_user: user_a)
end
fab!(:user_b, :user)
fab!(:user_b_silenced) do
Fabricate(:user_history, action: UserHistory.actions[:silence_user], target_user: user_b)
end
# Relates to
# meta.discourse.org/t/-/387508
it "refreshes the filter when navigating thought the action logs button" do
admin_users_page.visit
admin_users_page.user_row(user_a.id).username.click
admin_user_page.click_action_logs_button
expect(staff_action_logs_page).to have_log_row(user_a_silenced)
expect(staff_action_logs_page).to have_no_log_row(user_b_silenced)
page.go_back # navigate back to user page
page.go_back # navigate back to user list
admin_users_page.user_row(user_b.id).username.click
admin_user_page.click_action_logs_button
expect(staff_action_logs_page).to have_log_row(user_b_silenced)
expect(staff_action_logs_page).to have_no_log_row(user_a_silenced)
end
end
end
end