otwarchive-symphonyarchive/spec/controllers/admin/admin_users_controller_spec.rb

785 lines
28 KiB
Ruby
Raw Permalink Normal View History

2026-03-11 22:22:11 +00:00
# frozen_string_literal: true
require "spec_helper"
describe Admin::AdminUsersController do
include LoginMacros
include RedirectExpectationHelper
let(:admin) { create(:admin) }
let(:user) { create(:user) }
manage_roles = %w[superadmin legal open_doors policy_and_abuse support tag_wrangling].freeze
search_roles = %w[superadmin legal open_doors policy_and_abuse support tag_wrangling].freeze
search_past_roles = %w[superadmin open_doors policy_and_abuse support tag_wrangling].freeze
shared_examples "an action unauthorized admins can't access" do |authorized_roles:|
before { fake_login_admin(admin) }
context "with no role" do
let(:admin) { create(:admin, roles: []) }
it "redirects with an error" do
subject.call
it_redirects_to_with_error(root_url, "Sorry, only an authorized admin can access the page you were trying to reach.")
end
end
(Admin::VALID_ROLES - authorized_roles).each do |role|
context "with role #{role}" do
let(:admin) { create(:admin, roles: [role]) }
it "redirects with an error" do
subject.call
it_redirects_to_with_error(root_url, "Sorry, only an authorized admin can access the page you were trying to reach.")
end
end
end
end
shared_examples "an action guests and logged in users can't access" do
it "redirects with notice when logged out" do
subject.call
it_redirects_to_with_notice(root_url, "I'm sorry, only an admin can look at that area")
end
it "redirects with notice when logged in" do
fake_login
subject.call
it_redirects_to_with_notice(root_url, "I'm sorry, only an admin can look at that area")
end
end
shared_examples "an action authorized admins can access" do |authorized_roles:|
before { fake_login_admin(admin) }
authorized_roles.each do |role|
context "with role #{role}" do
let(:admin) { create(:admin, roles: [role]) }
it "responds with HTTP success status" do
subject.call
expect(response).to have_http_status(:success)
end
end
end
end
describe "GET #index" do
subject { -> { get :index } }
it_behaves_like "an action guests and logged in users can't access"
context "when logged in as admin" do
it_behaves_like "an action unauthorized admins can't access",
authorized_roles: search_roles
it_behaves_like "an action authorized admins can access",
authorized_roles: search_roles
end
context "when passing search_past", user_search: true do
subject { -> { get :index, params: { name: "old", search_past: "1" } } }
let(:user) { create(:user, login: "old") }
before do
user.update!(login: "new")
run_all_indexing_jobs
end
shared_examples "a search unauthorized admins can't perform" do |authorized_roles:|
(Admin::VALID_ROLES - authorized_roles).each do |role|
context "with role #{role}" do
let(:admin) { create(:admin, roles: [role]) }
it "does not find the user" do
subject.call
expect(assigns(:users)).to be_nil
end
end
end
end
shared_examples "a search authorized admins can perform" do |authorized_roles:|
before { fake_login_admin(admin) }
authorized_roles.each do |role|
context "with role #{role}" do
let(:admin) { create(:admin, roles: [role]) }
it "finds the user" do
subject.call
expect(assigns(:users)).to include(user)
end
end
end
end
it_behaves_like "a search unauthorized admins can't perform",
authorized_roles: search_past_roles
it_behaves_like "a search authorized admins can perform",
authorized_roles: search_past_roles
end
end
describe "GET #bulk_search" do
subject { -> { get :bulk_search } }
it_behaves_like "an action guests and logged in users can't access"
context "when logged in as admin" do
it_behaves_like "an action unauthorized admins can't access",
authorized_roles: search_roles
it_behaves_like "an action authorized admins can access",
authorized_roles: search_roles
end
end
describe "GET #show" do
subject { -> { get :show, params: { id: user.login } } }
it_behaves_like "an action guests and logged in users can't access"
context "when logged in as admin" do
it_behaves_like "an action unauthorized admins can't access",
authorized_roles: search_roles
it_behaves_like "an action authorized admins can access",
authorized_roles: search_roles
end
context "when admin has correct authorization" do
let(:admin) { create(:policy_and_abuse_admin) }
it "if user exists, allows access to show page" do
fake_login_admin(admin)
get :show, params: { id: user.login }
expect(response).to have_http_status(:success)
end
it "if user does not exist, raises a 404" do
fake_login_admin(admin)
params = { id: "not_existing_id" }
expect { get :show, params: params }
.to raise_error ActiveRecord::RecordNotFound
end
end
end
describe "PUT #update" do
subject { -> { put :update, params: { id: user.login, user: { roles: [] } } } }
let(:old_role) { create(:role) }
let(:role) { create(:role) }
let(:user) { create(:user, email: "user@example.com", roles: [old_role]) }
it_behaves_like "an action guests and logged in users can't access"
context "when logged in as an unauthorized admin" do
it_behaves_like "an action unauthorized admins can't access",
authorized_roles: manage_roles
end
context "when logged in as an authorized admin" do
before do
fake_login_admin(admin)
end
%w[policy_and_abuse superadmin].each do |admin_role|
context "with role #{admin_role}" do
before do
admin.update!(roles: [admin_role])
old_role.update!(name: "no_resets")
role.update!(name: "protected_user")
end
it "allows admins to update all attributes" do
expect do
put :update, params: {
id: user.login,
user: {
email: "updated@example.com",
roles: [role.id.to_s]
}
}
end.to change { user.reload.roles.pluck(:name) }
.from([old_role.name])
.to([role.name])
.and change { user.reload.email }
.from("user@example.com")
.to("updated@example.com")
it_redirects_to_with_notice(root_path, "User was successfully updated.")
end
end
end
context "with role open_doors" do
before do
admin.update!(roles: ["open_doors"])
old_role.update!(name: "archivist")
role.update!(name: "opendoors")
end
it "does not allow updating email" do
expect do
put :update, params: { id: user.login, user: { email: "updated@example.com" } }
end.to raise_exception(ActionController::UnpermittedParameters)
expect(user.reload.email).not_to eq("updated@example.com")
end
it "allows updating roles" do
role_no_resets = create(:role, name: "no_resets")
expect do
put :update, params: { id: user.login, user: { roles: [role.id.to_s, role_no_resets.id.to_s] } }
end.to change { user.reload.roles.pluck(:name) }
.from([old_role.name])
.to([role.name, role_no_resets.name])
.and avoid_changing { user.reload.email }
it_redirects_to_with_notice(root_path, "User was successfully updated.")
end
end
context "with role support" do
before do
admin.update!(roles: ["support"])
user.update!(roles: [])
role.update!(name: "no_resets")
end
it "allows updating email" do
put :update, params: { id: user.login, user: { email: "updated@example.com" } }
expect(user.reload.email).to eq("updated@example.com")
end
it "allows updating roles" do
expect do
put :update, params: { id: user.login, user: { roles: [role.id.to_s] } }
end.to change { user.reload.roles.pluck(:name) }
.from([])
.to([role.name])
.and avoid_changing { user.reload.email }
it_redirects_to_with_notice(root_path, "User was successfully updated.")
end
end
context "with role tag_wrangling" do
before do
admin.update!(roles: ["tag_wrangling"])
user.update!(roles: [])
role.update!(name: "tag_wrangler")
end
it "does not allow updating email" do
expect do
put :update, params: { id: user.login, user: { email: "updated@example.com" } }
end.to raise_exception(ActionController::UnpermittedParameters)
expect(user.reload.email).not_to eq("updated@example.com")
end
it "allows updating roles" do
expect do
put :update, params: { id: user.login, user: { roles: [role.id.to_s] } }
end.to change { user.reload.roles.pluck(:name) }
.from([])
.to([role.name])
.and avoid_changing { user.reload.email }
it_redirects_to_with_notice(root_path, "User was successfully updated.")
end
end
%w[legal].each do |admin_role|
context "with role #{admin_role}" do
let(:admin) { create(:admin, roles: [admin_role]) }
it "does not allow admin updating roles" do
expect do
put :update, params: { id: user.login, user: { roles: [role.id.to_s] } }
end.to raise_exception(ActionController::UnpermittedParameters)
expect(user.reload.roles).not_to include(role)
end
it "does not allow updating email" do
expect do
put :update, params: { id: user.login, user: { email: "updated@example.com" } }
end.to raise_exception(ActionController::UnpermittedParameters)
expect(user.reload.email).not_to eq("updated@example.com")
end
end
end
context "with available user roles restricted by type of admin" do
let!(:opendoors_role) { create(:role, name: "opendoors") }
let!(:archivist_role) { create(:role, name: "archivist") }
let!(:tag_wrangler_role) { create(:role, name: "tag_wrangler") }
before { admin.update!(roles: ["open_doors"]) }
it "does not add unpermitted roles" do
user.update!(roles: [opendoors_role])
put :update, params: {
id: user.login,
user: {
roles: [opendoors_role.id.to_s, tag_wrangler_role.id.to_s]
}
}
it_redirects_to_with_notice(root_path, "User was successfully updated.")
expect(user.reload.roles).to eq([opendoors_role])
end
it "does not change unpermitted roles when adding a role" do
user.update!(roles: [tag_wrangler_role])
put :update, params: {
id: user.login,
user: {
roles: [opendoors_role.id.to_s]
}
}
it_redirects_to_with_notice(root_path, "User was successfully updated.")
expect(user.reload.roles).to eq([opendoors_role, tag_wrangler_role])
end
it "does not change unpermitted roles when removing a role" do
user.update!(roles: [tag_wrangler_role, opendoors_role])
put :update, params: {
id: user.login,
user: {
roles: [""]
}
}
it_redirects_to_with_notice(root_path, "User was successfully updated.")
expect(user.reload.roles).to eq([tag_wrangler_role])
end
end
end
end
describe "POST #update_next_of_kin" do
subject { -> { post :update_next_of_kin, params: { user_login: user.login, next_of_kin_name: kin.login, next_of_kin_email: kin.email } } }
let(:kin) { create(:user) }
authorized_roles = %w[superadmin policy_and_abuse support].freeze
it_behaves_like "an action guests and logged in users can't access"
context "when logged in as an unauthorized admin" do
it_behaves_like "an action unauthorized admins can't access",
authorized_roles: authorized_roles
end
context "when logged in as an authorized admin" do
before do
fake_login_admin(admin)
end
authorized_roles.each do |role|
context "with role #{role}" do
let(:admin) { create(:admin, roles: [role]) }
it "adds next of kin and redirects with notice" do
subject.call
it_redirects_to_with_notice(admin_user_path(user), "Fannish next of kin was updated.")
expect(user.reload.fannish_next_of_kin.kin).to eq(kin)
expect(user.reload.fannish_next_of_kin.kin_email).to eq(kin.email)
end
it "logs adding a fannish next of kin" do
subject.call
user.reload
expect(user.fannish_next_of_kin.kin).to eq(kin)
log_item = user.log_items.last
expect(log_item.action).to eq(ArchiveConfig.ACTION_ADD_FNOK)
expect(log_item.fnok_user.id).to eq(kin.id)
added_log_item = kin.reload.log_items.last
expect(added_log_item.action).to eq(ArchiveConfig.ACTION_ADDED_AS_FNOK)
expect(added_log_item.fnok_user.id).to eq(user.id)
expect_changes_made_by(admin, [log_item, added_log_item])
end
it "logs removing a fannish next of kin" do
kin = create(:fannish_next_of_kin, user: user).kin
post :update_next_of_kin, params: {
user_login: user.login
}
user.reload
expect(user.fannish_next_of_kin).to be_nil
log_item = user.log_items.last
expect(log_item.action).to eq(ArchiveConfig.ACTION_REMOVE_FNOK)
expect(log_item.fnok_user.id).to eq(kin.id)
removed_log_item = kin.reload.log_items.last
expect(removed_log_item.action).to eq(ArchiveConfig.ACTION_REMOVED_AS_FNOK)
expect(removed_log_item.fnok_user.id).to eq(user.id)
expect_changes_made_by(admin, [log_item, removed_log_item])
end
it "logs updating a fannish next of kin" do
previous_kin = create(:fannish_next_of_kin, user: user).kin
subject.call
user.reload
expect(user.fannish_next_of_kin.kin).to eq(kin)
remove_log_item = user.log_items[-2]
expect(remove_log_item.action).to eq(ArchiveConfig.ACTION_REMOVE_FNOK)
expect(remove_log_item.fnok_user.id).to eq(previous_kin.id)
add_log_item = user.log_items.last
expect(add_log_item.action).to eq(ArchiveConfig.ACTION_ADD_FNOK)
expect(add_log_item.fnok_user.id).to eq(kin.id)
removed_log_item = previous_kin.reload.log_items.last
expect(removed_log_item.action).to eq(ArchiveConfig.ACTION_REMOVED_AS_FNOK)
expect(removed_log_item.fnok_user.id).to eq(user.id)
added_log_item = kin.reload.log_items.last
expect(added_log_item.action).to eq(ArchiveConfig.ACTION_ADDED_AS_FNOK)
expect(added_log_item.fnok_user.id).to eq(user.id)
expect_changes_made_by(admin, [remove_log_item, add_log_item, removed_log_item, added_log_item])
end
def expect_changes_made_by(admin, log_items)
log_items.each do |log_item|
expect(log_item.admin_id).to eq(admin.id)
expect(log_item.note).to eq("Change made by #{admin.login}")
end
end
it "does nothing if changing the fnok to themselves" do
previous_kin = create(:fannish_next_of_kin, user: user)
post :update_next_of_kin, params: {
user_login: user.login, next_of_kin_name: previous_kin.kin.login, next_of_kin_email: previous_kin.kin_email
}
it_redirects_to_with_notice(admin_user_path(user), "No change to fannish next of kin.")
expect(user.reload.log_items).to be_empty
end
it "errors if trying to add an incomplete fnok" do
post :update_next_of_kin, params: {
user_login: user.login, next_of_kin_email: ""
}
kin = assigns(:user).fannish_next_of_kin
expect(kin).not_to be_valid
expect(kin.errors[:kin_email]).to include("can't be blank")
expect(user.reload.log_items).to be_empty
end
end
end
end
end
describe "POST #update_status" do
before { fake_login_admin(admin) }
shared_examples "an unauthorized admin adding a note to a user" do
it "redirects with error" do
post :update_status, params: {
user_login: user.login, admin_action: "note", admin_note: "User likes me, user likes me not."
}
it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
end
end
shared_examples "an authorized admin adding a note to a user" do
it "saves note and redirects with notice" do
admin_note = "User likes me, user likes me not."
post :update_status, params: {
user_login: user.login, admin_action: "note", admin_note: admin_note
}
it_redirects_to_with_notice(admin_user_path(user), "Note was recorded.")
expect(user.reload.log_items.last.action).to eq(ArchiveConfig.ACTION_NOTE)
expect(user.log_items.last.note).to eq(admin_note)
end
end
shared_examples "an unauthorized admin suspending a user" do
it "redirects with error" do
post :update_status, params: {
user_login: user.login, admin_action: "suspend", suspend_days: "3", admin_note: "User violated community guidelines"
}
it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
expect(user.reload.suspended).to be_falsey
end
end
shared_examples "an authorized admin suspending a user" do
it "suspends user and redirects with notice" do
post :update_status, params: {
user_login: user.login, admin_action: "suspend", suspend_days: "3", admin_note: "User violated community guidelines"
}
it_redirects_to_with_notice(admin_user_path(user), "User has been temporarily suspended.")
expect(user.reload.suspended).to be_truthy
end
end
context "when the admin has no roles" do
before { admin.update!(roles: []) }
it_behaves_like "an unauthorized admin adding a note to a user"
it_behaves_like "an unauthorized admin suspending a user"
end
(Admin::VALID_ROLES - %w[policy_and_abuse support superadmin]).each do |role|
context "when the admin has #{role} role" do
it_behaves_like "an unauthorized admin adding a note to a user"
it_behaves_like "an unauthorized admin suspending a user"
end
end
%w[superadmin policy_and_abuse].each do |role|
context "when the admin has #{role} role" do
let(:admin) { create(:admin, roles: [role]) }
it_behaves_like "an authorized admin adding a note to a user"
it_behaves_like "an authorized admin suspending a user"
end
end
context "when the admin has support role" do
let(:admin) { create(:support_admin) }
it_behaves_like "an authorized admin adding a note to a user"
it_behaves_like "an unauthorized admin suspending a user"
end
end
describe "GET #confirm_delete_user_creations" do
subject { -> { get :confirm_delete_user_creations, params: { id: user.login } } }
let(:user) { create(:user, banned: true) }
authorized_roles = %w[superadmin policy_and_abuse].freeze
it_behaves_like "an action guests and logged in users can't access"
context "when logged in as an unauthorized admin" do
it_behaves_like "an action unauthorized admins can't access",
authorized_roles: authorized_roles
end
context "when logged in as an authorized admin" do
before { fake_login_admin(admin) }
authorized_roles.each do |role|
context "with role #{role}" do
let(:admin) { create(:admin, roles: [role]) }
context "when the user is not banned" do
it "redirects with error" do
user.update!(banned: false)
subject.call
it_redirects_to_with_error(admin_users_path, "That user is not banned!")
end
end
context "when the user is banned" do
it "allows admins to access delete user creations page" do
user.update!(banned: true)
subject.call
expect(response).to have_http_status(:success)
end
end
end
end
end
end
describe "POST #destroy_user_creations" do
subject { -> { post :destroy_user_creations, params: { id: user.login } } }
let!(:work) { create(:work, authors: [user.default_pseud]) }
let(:other_owner) { create(:user, banned: false) }
let!(:collection1) { create(:collection) }
let!(:collection2) { create(:collection) }
let!(:comment) { create(:comment, pseud: user.default_pseud) }
authorized_roles = %w[superadmin policy_and_abuse].freeze
before do
# Banning user only after creating works for them
user.update!(banned: true)
collection1.collection_participants = [create(:collection_participant, user: user, collection: collection1, participant_role: CollectionParticipant::OWNER)]
collection1.save
create(:collection_participant, user: other_owner, collection: collection2, participant_role: CollectionParticipant::OWNER)
create(:collection_participant, user: user, collection: collection2, participant_role: CollectionParticipant::MEMBER)
end
it_behaves_like "an action guests and logged in users can't access"
context "when logged in as an unauthorized admin" do
it_behaves_like "an action unauthorized admins can't access",
authorized_roles: authorized_roles
end
context "when logged in as an authorized admin" do
before do
fake_login_admin(admin)
end
authorized_roles.each do |role|
context "with role #{role}" do
let(:admin) { create(:admin, roles: [role]) }
context "when the user is not banned" do
it "redirects with error" do
user.update!(banned: false)
subject.call
it_redirects_to_with_error(admin_users_path, "That user is not banned!")
end
end
context "when the user is banned" do
it "destroys the user's creations and redirects with success notice" do
subject.call
# Check that the first user's collection is deleted
expect(Collection.exists?(collection1.id)).to be_falsey
# Check that the second user's collection still exists
expect(Collection.exists?(other_owner.collections.last.id)).to be_truthy
it_redirects_to_with_notice(admin_users_path, "All creations by user #{user.login} have been deleted.")
expect(Work.exists?(work.id)).to be false
expect(Comment.exists?(comment.id)).to be_falsey
end
it "sends all of the user's comments as spam reports to Akismet" do
expect_any_instance_of(Comment).to receive(:submit_spam)
subject.call
end
it "does not send deleted comments to Akismet" do
comment.is_deleted = true
comment.save!(validate: false)
expect_any_instance_of(Comment).to_not receive(:submit_spam)
subject.call
end
it "deletes marked-as-deleted comments when all its replies are deleted" do
reply = create(:comment, commentable: comment, pseud: user.default_pseud)
comment.destroy_or_mark_deleted
expect(comment.is_deleted).to be_truthy
subject.call
expect(Comment.exists?(comment.id)).to be_falsey
expect(Comment.exists?(reply.id)).to be_falsey
end
end
end
end
end
end
describe "GET #troubleshoot" do
subject { -> { get :troubleshoot, params: { id: user.login } } }
it_behaves_like "an action guests and logged in users can't access"
context "when logged in as an unauthorized admin" do
it_behaves_like "an action unauthorized admins can't access",
authorized_roles: manage_roles
end
context "when logged in as an authorized admin" do
manage_roles.each do |role|
context "with role #{role}" do
let(:admin) { create(:admin, roles: [role]) }
it "troubleshoots account and redirects with notice" do
fake_login_admin(admin)
subject.call
it_redirects_to_with_notice(root_path, "User account troubleshooting complete.")
end
end
end
end
end
describe "POST #activate" do
subject { -> { post :activate, params: { id: user.login } } }
it_behaves_like "an action guests and logged in users can't access"
context "when logged in as an unauthorized admin" do
it_behaves_like "an action unauthorized admins can't access",
authorized_roles: manage_roles
end
context "when logged in as an authorized admin" do
manage_roles.each do |role|
context "with role #{role}" do
let(:admin) { create(:admin, roles: [role]) }
it "activates the user account, creates a log item, and redirects with notice" do
fake_login_admin(admin)
subject.call
log_item = user.reload.log_items.last
expect(log_item.action).to eq(ArchiveConfig.ACTION_ACTIVATE)
expect(log_item.note).to eq("Manually Activated")
expect(log_item.admin).to eq(admin)
it_redirects_to_with_notice(admin_user_path(id: user.login), "User Account Activated")
end
end
end
end
end
describe "GET #creations" do
subject { -> { get :creations, params: { id: user.login } } }
authorized_roles = %w[policy_and_abuse superadmin]
it_behaves_like "an action guests and logged in users can't access"
context "when logged in as an unauthorized admin" do
it_behaves_like "an action unauthorized admins can't access",
authorized_roles: authorized_roles
end
context "when logged in as an authorized admin" do
authorized_roles.each do |role|
context "with #{role} role" do
let(:admin) { create(:admin, roles: [role]) }
let!(:user_comment) { create(:comment, pseud: user.default_pseud) }
let!(:other_comment) { create(:comment) }
let!(:user_work) { create(:work, authors: [user.default_pseud]) }
let!(:other_work) { create(:work) }
before { fake_login_admin(admin) }
it "renders creations template and assigns comments and works" do
subject.call
expect(response).to render_template(:creations)
expect(assigns[:comments]).to contain_exactly(user_comment)
expect(assigns[:works]).to contain_exactly(user_work)
end
it "raises ActiveRecord::RecordNotFound when user does not exist" do
expect do
get :creations, params: { id: "nonexistent_user" }
end.to raise_exception(ActiveRecord::RecordNotFound)
end
end
end
end
end
end