Files
sure/test/controllers/admin/users_controller_test.rb
T
Josh ca66346dc5 feat: add safe admin user removal (#3131)
* feat: add safe admin user removal

* fix: address user removal review findings

* fix: close remaining user removal review gaps

* fix: handle deleted users during session creation

* fix: fail closed when session creation fails

* fix: reject token issuance for inactive users
2026-08-22 21:54:32 +02:00

197 lines
6.4 KiB
Ruby

require "test_helper"
class Admin::UsersControllerTest < ActionDispatch::IntegrationTest
include ActiveJob::TestHelper
setup do
sign_in users(:sure_support_staff)
end
test "index groups users by family sorted by transaction count" do
family_with_more = users(:family_admin).family
family_with_fewer = users(:empty).family
account = Account.create!(family: family_with_more, name: "Test", balance: 0, currency: "USD", accountable: Depository.new)
3.times { |i| account.entries.create!(name: "Txn #{i}", date: Date.current, amount: 10, currency: "USD", entryable: Transaction.new) }
get admin_users_url
assert_response :success
body = response.body
more_idx = body.index(family_with_more.name)
fewer_idx = body.index(family_with_fewer.name)
assert_not_nil more_idx
assert_not_nil fewer_idx
assert_operator more_idx, :<, fewer_idx,
"Family with more transactions should appear before family with fewer"
end
test "index shows subscription status for families" do
family = users(:family_admin).family
family.subscription&.destroy
Subscription.create!(
family_id: family.id,
status: :active,
stripe_id: "cus_test_#{family.id}"
)
get admin_users_url
assert_response :success
assert_match(/Active/, response.body, "Page should show subscription status for families with active subscriptions")
end
test "index shows no subscription label for families without subscription" do
users(:family_admin).family.subscription&.destroy
get admin_users_url
assert_response :success
assert_match(/No subscription/, response.body, "Page should show 'No subscription' for families without one")
end
test "index shows removed SSO identities with a recovery action" do
block = SsoIdentityBlock.create!(
provider: "openid_connect",
uid_digest: SsoIdentityBlock.digest("blocked-subject"),
identity_label: "removed-user@example.com"
)
get admin_users_url
assert_response :success
assert_select "form[action=?]", admin_sso_identity_block_path(block)
assert_match block.identity_label, response.body
end
test "super admin permanently removes a user and revokes their credentials" do
target = users(:family_member)
target_email = target.email
removed_identity = target.oidc_identities.first!
removed_provider = removed_identity.provider
removed_uid = removed_identity.uid
target.sessions.create!
oauth_app = Doorkeeper::Application.create!(
name: "Removal test",
redirect_uri: "https://app.example/callback",
confidential: false
)
oauth_token = Doorkeeper::AccessToken.create!(
application: oauth_app,
resource_owner_id: target.id,
scopes: "read_write",
use_refresh_token: true
)
assert target.oidc_identities.exists?
assert target.api_keys.exists?
assert_difference -> { SsoAuditLog.by_event("user_removed").count }, 1 do
assert_enqueued_with(job: UserPurgeJob, args: [ target ]) do
delete admin_user_url(target), params: { confirmation_email: target_email }
end
end
assert_redirected_to admin_users_path
target.reload
assert_not target.active?
assert_empty target.sessions
assert_empty target.oidc_identities
assert_empty target.api_keys
assert oauth_token.reload.revoked?
assert SsoIdentityBlock.blocked?(provider: removed_provider, uid: removed_uid)
identity_block = SsoIdentityBlock.find_by!(provider: removed_provider)
if SsoIdentityBlock.encryption_ready?
assert_equal target_email, identity_block.identity_label
else
assert_not_equal target_email, identity_block.identity_label
end
audit_log = SsoAuditLog.by_event("user_removed").order(:created_at).last
assert_equal target.id, audit_log.metadata.fetch("target_user_id")
assert_not audit_log.metadata.key?("target_email")
assert_equal users(:sure_support_staff).id, audit_log.metadata.fetch("actor_user_id")
end
test "super admin cannot remove themselves" do
me = users(:sure_support_staff)
assert_no_enqueued_jobs only: UserPurgeJob do
delete admin_user_url(me)
end
assert_redirected_to admin_users_path
assert User.exists?(me.id)
assert me.reload.active?
end
test "audit failure rolls back user removal" do
target = users(:family_member)
target_email = target.email
SsoAuditLog.expects(:log_user_removed!).raises("audit failure")
assert_no_enqueued_jobs only: UserPurgeJob do
assert_raises(RuntimeError, match: /audit failure/) do
delete admin_user_url(target), params: { confirmation_email: target_email }
end
end
assert target.reload.active?
assert target.oidc_identities.exists?
end
test "inactive super admin cannot use an existing session to remove the last active super admin" do
target = users(:family_admin)
target.update!(role: :super_admin)
User.where(role: :super_admin).where.not(id: target.id).update_all(active: false)
assert_no_enqueued_jobs only: UserPurgeJob do
delete admin_user_url(target), params: { confirmation_email: target.email }
end
assert_redirected_to new_session_path
assert User.exists?(target.id)
assert target.reload.active?
end
test "deletion page redirects instead of erroring when targeting yourself" do
me = users(:sure_support_staff)
get deletion_admin_user_url(me)
assert_redirected_to admin_users_path
assert_match(/cannot remove your own account/i, flash[:alert].to_s)
end
test "deletion confirmation requires the target email" do
target = users(:family_member)
assert_no_enqueued_jobs only: UserPurgeJob do
delete admin_user_url(target), params: { confirmation_email: "wrong@example.com" }
end
assert_redirected_to admin_users_path
assert target.reload.active?
end
test "deletion page renders a typed email confirmation dialog" do
target = users(:family_member)
get deletion_admin_user_url(target)
assert_response :success
assert_select "dialog"
assert_select "input[name=confirmation_email][required]"
assert_includes response.body, target.email
end
test "non super admin cannot remove a user" do
sign_in users(:family_member)
target = users(:family_admin)
assert_no_enqueued_jobs only: UserPurgeJob do
delete admin_user_url(target)
end
assert_redirected_to root_path
assert User.exists?(target.id)
end
end