From d16f105f15f8f829a87760081796577c8c5fcb4f Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 13 Jan 2026 09:18:25 -0600 Subject: [PATCH] more tests --- test/towerops_web/user_auth_test.exs | 315 +++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) diff --git a/test/towerops_web/user_auth_test.exs b/test/towerops_web/user_auth_test.exs index 0bd381f1..40061e29 100644 --- a/test/towerops_web/user_auth_test.exs +++ b/test/towerops_web/user_auth_test.exs @@ -3,6 +3,7 @@ defmodule ToweropsWeb.UserAuthTest do import Towerops.AccountsFixtures + alias Phoenix.LiveView.Socket alias Towerops.Accounts alias Towerops.Accounts.Scope alias ToweropsWeb.UserAuth @@ -460,4 +461,318 @@ defmodule ToweropsWeb.UserAuthTest do refute get_session(conn, :impersonating) end end + + describe "start_impersonation/2" do + test "allows superuser to impersonate regular user", %{conn: conn} do + superuser = + user_fixture() + |> Ecto.Changeset.change(%{is_superuser: true}) + |> Towerops.Repo.update!() + + target_user = user_fixture() + + conn = + conn + |> Map.put(:remote_ip, {127, 0, 0, 1}) + |> fetch_flash() + |> assign(:current_scope, Scope.for_user(superuser)) + |> UserAuth.start_impersonation(target_user.id) + + assert get_session(conn, :impersonating) + assert get_session(conn, :superuser_id) == superuser.id + assert get_session(conn, :target_user_id) == target_user.id + assert conn.assigns.current_scope.user.id == target_user.id + assert conn.assigns.current_scope.superuser.id == superuser.id + assert redirected_to(conn) == ~p"/orgs" + end + + test "prevents superuser from impersonating self", %{conn: conn} do + superuser = + user_fixture() + |> Ecto.Changeset.change(%{is_superuser: true}) + |> Towerops.Repo.update!() + + conn = + conn + |> Map.put(:remote_ip, {127, 0, 0, 1}) + |> fetch_flash() + |> assign(:current_scope, Scope.for_user(superuser)) + |> UserAuth.start_impersonation(superuser.id) + + # Check that the error was set + assert Phoenix.Flash.get(conn.assigns.flash, :error) == + "You cannot impersonate yourself." + + assert conn.halted + end + + test "prevents superuser from impersonating other superusers", %{conn: conn} do + superuser = + user_fixture() + |> Ecto.Changeset.change(%{is_superuser: true}) + |> Towerops.Repo.update!() + + other_superuser = + user_fixture() + |> Ecto.Changeset.change(%{is_superuser: true}) + |> Towerops.Repo.update!() + + conn = + conn + |> Map.put(:remote_ip, {127, 0, 0, 1}) + |> fetch_flash() + |> assign(:current_scope, Scope.for_user(superuser)) + |> UserAuth.start_impersonation(other_superuser.id) + + # Check that the error was set + assert Phoenix.Flash.get(conn.assigns.flash, :error) == + "You cannot impersonate other superusers." + + assert conn.halted + end + end + + describe "stop_impersonation/1" do + test "stops impersonation and restores superuser session", %{conn: conn} do + superuser = + user_fixture() + |> Ecto.Changeset.change(%{is_superuser: true}) + |> Towerops.Repo.update!() + + target_user = user_fixture() + + {:ok, organization} = + Towerops.Organizations.create_organization(%{name: "Super Org"}, superuser.id) + + conn = + conn + |> Map.put(:remote_ip, {127, 0, 0, 1}) + |> fetch_flash() + |> put_session(:impersonating, true) + |> put_session(:superuser_id, superuser.id) + |> put_session(:target_user_id, target_user.id) + |> assign(:current_scope, Scope.for_impersonation(superuser, target_user)) + |> UserAuth.stop_impersonation() + + refute get_session(conn, :impersonating) + refute get_session(conn, :superuser_id) + refute get_session(conn, :target_user_id) + assert conn.assigns.current_scope.user.id == superuser.id + assert redirected_to(conn) == ~p"/orgs/#{organization.slug}/equipment" + end + + test "redirects to /orgs when superuser has no organizations", %{conn: conn} do + superuser = + user_fixture() + |> Ecto.Changeset.change(%{is_superuser: true}) + |> Towerops.Repo.update!() + + target_user = user_fixture() + + conn = + conn + |> Map.put(:remote_ip, {127, 0, 0, 1}) + |> fetch_flash() + |> put_session(:impersonating, true) + |> put_session(:superuser_id, superuser.id) + |> put_session(:target_user_id, target_user.id) + |> assign(:current_scope, Scope.for_impersonation(superuser, target_user)) + |> UserAuth.stop_impersonation() + + assert redirected_to(conn) == ~p"/orgs" + end + end + + describe "on_mount/4 - :redirect_if_user_is_authenticated" do + test "redirects authenticated users to signed in path", %{user: user} do + user_token = Accounts.generate_user_session_token(user) + + {:halt, socket} = + UserAuth.on_mount( + :redirect_if_user_is_authenticated, + %{}, + %{"user_token" => user_token}, + %Socket{} + ) + + assert socket.redirected + end + + test "continues for unauthenticated users" do + {:cont, socket} = + UserAuth.on_mount( + :redirect_if_user_is_authenticated, + %{}, + %{}, + %Socket{} + ) + + refute socket.redirected + end + end + + describe "on_mount/4 - :require_authenticated_user" do + test "allows authenticated users", %{user: user} do + user_token = Accounts.generate_user_session_token(user) + + socket = %Socket{assigns: %{__changed__: %{}, flash: %{}}} + + {:cont, socket} = + UserAuth.on_mount( + :require_authenticated_user, + %{}, + %{"user_token" => user_token}, + socket + ) + + assert socket.assigns.current_scope.user.id == user.id + end + + test "redirects unauthenticated users to login" do + socket = %Socket{assigns: %{__changed__: %{}, flash: %{}}} + + {:halt, socket} = + UserAuth.on_mount( + :require_authenticated_user, + %{}, + %{}, + socket + ) + + assert socket.redirected + end + end + + describe "on_mount/4 - :load_current_organization" do + test "loads organization for authenticated member", %{user: user} do + {:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id) + user_token = Accounts.generate_user_session_token(user) + + socket = %Socket{assigns: %{__changed__: %{}, flash: %{}}} + + socket = + Phoenix.Component.assign_new(socket, :current_scope, fn -> Scope.for_user(user) end) + + {:cont, socket} = + UserAuth.on_mount( + :load_current_organization, + %{"org_slug" => organization.slug}, + %{"user_token" => user_token}, + socket + ) + + assert socket.assigns.current_organization.id == organization.id + assert socket.assigns.current_membership.role == :owner + end + + test "redirects non-members", %{user: user} do + other_user = user_fixture() + + {:ok, organization} = + Towerops.Organizations.create_organization(%{name: "Other Org"}, other_user.id) + + user_token = Accounts.generate_user_session_token(user) + + socket = %Socket{assigns: %{__changed__: %{}, flash: %{}}} + + socket = + Phoenix.Component.assign_new(socket, :current_scope, fn -> Scope.for_user(user) end) + + {:halt, socket} = + UserAuth.on_mount( + :load_current_organization, + %{"org_slug" => organization.slug}, + %{"user_token" => user_token}, + socket + ) + + assert socket.redirected + end + + test "redirects when organization not found", %{user: user} do + user_token = Accounts.generate_user_session_token(user) + + socket = %Socket{assigns: %{__changed__: %{}, flash: %{}}} + + socket = + Phoenix.Component.assign_new(socket, :current_scope, fn -> Scope.for_user(user) end) + + {:halt, socket} = + UserAuth.on_mount( + :load_current_organization, + %{"org_slug" => "non-existent"}, + %{"user_token" => user_token}, + socket + ) + + assert socket.redirected + end + + test "continues when no org_slug in params" do + socket = %Socket{assigns: %{__changed__: %{}, flash: %{}}} + + {:cont, socket} = + UserAuth.on_mount( + :load_current_organization, + %{}, + %{}, + socket + ) + + refute socket.redirected + end + end + + describe "on_mount/4 - :require_superuser" do + test "allows superusers" do + superuser = + user_fixture() + |> Ecto.Changeset.change(%{is_superuser: true}) + |> Towerops.Repo.update!() + + user_token = Accounts.generate_user_session_token(superuser) + + socket = %Socket{assigns: %{__changed__: %{}, flash: %{}}} + + {:cont, socket} = + UserAuth.on_mount( + :require_superuser, + %{}, + %{"user_token" => user_token}, + socket + ) + + assert socket.assigns.current_scope.user.id == superuser.id + end + + test "redirects regular users", %{user: user} do + user_token = Accounts.generate_user_session_token(user) + + socket = %Socket{assigns: %{__changed__: %{}, flash: %{}}} + + {:halt, socket} = + UserAuth.on_mount( + :require_superuser, + %{}, + %{"user_token" => user_token}, + socket + ) + + assert socket.redirected + end + + test "redirects unauthenticated users" do + socket = %Socket{assigns: %{__changed__: %{}, flash: %{}}} + + {:halt, socket} = + UserAuth.on_mount( + :require_superuser, + %{}, + %{}, + socket + ) + + assert socket.redirected + end + end end