# frozen_string_literal: true require "test_helper" class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest setup do @user = users(:family_admin) @user.api_keys.active.destroy_all @api_key = ApiKey.create!( user: @user, name: "Test Read-Write Key", scopes: [ "read_write" ], display_key: "test_rw_#{SecureRandom.hex(8)}" ) @read_only_api_key = ApiKey.create!( user: @user, name: "Test Read-Only Key", scopes: [ "read" ], display_key: "test_ro_#{SecureRandom.hex(8)}", source: "mobile" ) end # -- Authentication -------------------------------------------------------- test "reset requires authentication" do delete "/api/v1/users/reset" assert_response :unauthorized end test "destroy requires authentication" do delete "/api/v1/users/me" assert_response :unauthorized end # -- Scope enforcement ----------------------------------------------------- test "reset requires write scope" do delete "/api/v1/users/reset", headers: api_headers(@read_only_api_key) assert_response :forbidden end test "destroy requires write scope" do delete "/api/v1/users/me", headers: api_headers(@read_only_api_key) assert_response :forbidden end # -- Reset ----------------------------------------------------------------- test "reset enqueues FamilyResetJob and returns 200" do assert_enqueued_with(job: FamilyResetJob) do delete "/api/v1/users/reset", headers: api_headers(@api_key) end assert_response :ok body = JSON.parse(response.body) assert_equal "Account reset has been initiated", body["message"] end # -- Delete account -------------------------------------------------------- test "destroy deactivates user and returns 200" do solo_family = Family.create!(name: "Solo Family", currency: "USD", locale: "en", date_format: "%m-%d-%Y") solo_user = solo_family.users.create!( email: "solo@example.com", password: "password123", password_confirmation: "password123", role: :admin ) solo_api_key = ApiKey.create!( user: solo_user, name: "Solo Key", scopes: [ "read_write" ], display_key: "test_solo_#{SecureRandom.hex(8)}" ) delete "/api/v1/users/me", headers: api_headers(solo_api_key) assert_response :ok body = JSON.parse(response.body) assert_equal "Account has been deleted", body["message"] solo_user.reload assert_not solo_user.active? assert_not_equal "solo@example.com", solo_user.email end test "destroy returns 422 when admin has other family members" do delete "/api/v1/users/me", headers: api_headers(@api_key) assert_response :unprocessable_entity body = JSON.parse(response.body) assert_equal "Failed to delete account", body["error"] end # -- Deactivated user ------------------------------------------------------ test "rejects deactivated user with 401" do @user.update_column(:active, false) delete "/api/v1/users/reset", headers: api_headers(@api_key) assert_response :unauthorized body = JSON.parse(response.body) assert_equal "Account has been deactivated", body["message"] end private def api_headers(api_key) { "X-Api-Key" => api_key.display_key } end end