defmodule ToweropsWeb.AdminControllerTest do use ToweropsWeb.ConnCase, async: true import Towerops.AccountsFixtures import Towerops.OrganizationsFixtures describe "POST /admin/impersonate/:user_id" do test "superuser can start impersonating another user", %{conn: conn} do superuser = user_fixture() |> Ecto.Changeset.change(%{is_superuser: true}) |> Towerops.Repo.update!() _org = organization_fixture(superuser.id) target_user = user_fixture(enable_totp: true) _target_org = organization_fixture(target_user.id) conn = conn |> log_in_user(superuser) |> post(~p"/admin/impersonate/#{target_user.id}") assert redirected_to(conn) == ~p"/orgs" assert get_session(conn, :impersonating) == true assert get_session(conn, :superuser_id) == superuser.id assert get_session(conn, :target_user_id) == target_user.id end test "superuser cannot impersonate themselves", %{conn: conn} do superuser = user_fixture() |> Ecto.Changeset.change(%{is_superuser: true}) |> Towerops.Repo.update!() _org = organization_fixture(superuser.id) conn = conn |> log_in_user(superuser) |> post(~p"/admin/impersonate/#{superuser.id}") assert redirected_to(conn) == ~p"/admin/users" assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "cannot impersonate yourself" end test "non-superuser is redirected away", %{conn: conn} do user = user_fixture(enable_totp: true) _org = organization_fixture(user.id) target_user = user_fixture(enable_totp: true) conn = conn |> log_in_user(user) |> post(~p"/admin/impersonate/#{target_user.id}") assert redirected_to(conn) == ~p"/orgs" end test "unauthenticated user is redirected to login", %{conn: conn} do target_user = user_fixture(enable_totp: true) conn = post(conn, ~p"/admin/impersonate/#{target_user.id}") assert redirected_to(conn) == ~p"/users/log-in" end end describe "DELETE /admin/impersonate" do test "superuser can stop impersonation of another superuser", %{conn: conn} do superuser = [enable_totp: true] |> user_fixture() |> Ecto.Changeset.change(%{is_superuser: true}) |> Towerops.Repo.update!() _org = organization_fixture(superuser.id) # Impersonating another superuser so require_superuser passes # (scope.user is the target, which must be superuser to reach the action) other_superuser = [enable_totp: true] |> user_fixture() |> Ecto.Changeset.change(%{is_superuser: true}) |> Towerops.Repo.update!() _target_org = organization_fixture(other_superuser.id) conn = conn |> log_in_user(superuser) |> put_session(:impersonating, true) |> put_session(:superuser_id, superuser.id) |> put_session(:target_user_id, other_superuser.id) |> delete(~p"/admin/impersonate") # stop_impersonation redirects to signed_in_path for the superuser assert redirected_to(conn) =~ ~r"^/(dashboard|orgs)$" assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Stopped impersonating" end test "impersonating non-superuser cannot reach stop endpoint via pipeline", %{conn: conn} do # When impersonating a non-superuser, require_superuser blocks the request # because scope.user is the target (non-superuser) user. superuser = user_fixture() |> Ecto.Changeset.change(%{is_superuser: true}) |> Towerops.Repo.update!() _org = organization_fixture(superuser.id) target_user = user_fixture(enable_totp: true) conn = conn |> log_in_user(superuser) |> put_session(:impersonating, true) |> put_session(:superuser_id, superuser.id) |> put_session(:target_user_id, target_user.id) |> delete(~p"/admin/impersonate") # Pipeline redirects because scope.user (target) is not a superuser assert redirected_to(conn) == ~p"/orgs" end test "non-superuser is redirected away", %{conn: conn} do user = user_fixture(enable_totp: true) _org = organization_fixture(user.id) conn = conn |> log_in_user(user) |> delete(~p"/admin/impersonate") assert redirected_to(conn) == ~p"/orgs" end end describe "POST /admin/oban/flush" do setup %{conn: conn} do superuser = user_fixture() |> Ecto.Changeset.change(%{is_superuser: true}) |> Towerops.Repo.update!() _org = organization_fixture(superuser.id) conn = log_in_user(conn, superuser) %{conn: conn} end test "deletes jobs in valid states and returns count", %{conn: conn} do conn = post(conn, "/admin/oban/flush?states=scheduled,retryable") response = json_response(conn, 200) assert response["ok"] == true assert is_integer(response["deleted"]) assert response["states"] == ["scheduled", "retryable"] end test "returns 400 when no valid states are provided", %{conn: conn} do conn = post(conn, "/admin/oban/flush?states=garbage,banana") assert json_response(conn, 400)["error"] =~ "No valid states" end test "filters out invalid states but keeps valid ones", %{conn: conn} do conn = post(conn, "/admin/oban/flush?states=scheduled,invalid") response = json_response(conn, 200) assert response["states"] == ["scheduled"] end end end