Add tests for system actor protection and hiding

Index: system actor not in list, destroy returns Ash.Error.Invalid. Show/Form:
redirect to /users when viewing or editing system actor user.
This commit is contained in:
Moritz 2026-01-27 14:29:26 +01:00 committed by moritz
parent 8ad5201e1a
commit 9c31f0c16c
4 changed files with 57 additions and 8 deletions

View file

@ -10,6 +10,14 @@ defmodule Mv.Helpers.SystemActorTest do
require Ash.Query require Ash.Query
# Deletes a user row directly via SQL, bypassing Ash validations.
# Use only in tests when setting up "no system user" / "no users" scenarios;
# Ash.destroy! forbids deleting the system actor user.
defp delete_user_bypass_ash(user) do
id = Ecto.UUID.dump!(user.id)
Ecto.Adapters.SQL.query!(Mv.Repo, "DELETE FROM users WHERE id = $1", [id])
end
# Helper function to ensure admin role exists # Helper function to ensure admin role exists
defp ensure_admin_role do defp ensure_admin_role do
case Authorization.list_roles() do case Authorization.list_roles() do
@ -124,7 +132,7 @@ defmodule Mv.Helpers.SystemActorTest do
|> Ash.Query.filter(email == ^"system@mila.local") |> Ash.Query.filter(email == ^"system@mila.local")
|> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do |> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do
{:ok, user} when not is_nil(user) -> {:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts, actor: system_actor) delete_user_bypass_ash(user)
_ -> _ ->
:ok :ok
@ -163,7 +171,7 @@ defmodule Mv.Helpers.SystemActorTest do
|> Ash.Query.filter(email == ^"system@mila.local") |> Ash.Query.filter(email == ^"system@mila.local")
|> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do |> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do
{:ok, user} when not is_nil(user) -> {:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts, actor: system_actor) delete_user_bypass_ash(user)
_ -> _ ->
:ok :ok
@ -177,7 +185,7 @@ defmodule Mv.Helpers.SystemActorTest do
|> Ash.Query.filter(email == ^admin_email) |> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do |> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do
{:ok, user} when not is_nil(user) -> {:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts, actor: system_actor) delete_user_bypass_ash(user)
_ -> _ ->
:ok :ok
@ -227,7 +235,7 @@ defmodule Mv.Helpers.SystemActorTest do
|> Ash.Query.filter(email == ^"system@mila.local") |> Ash.Query.filter(email == ^"system@mila.local")
|> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do |> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do
{:ok, user} when not is_nil(user) -> {:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts, actor: system_actor) delete_user_bypass_ash(user)
_ -> _ ->
:ok :ok
@ -241,7 +249,7 @@ defmodule Mv.Helpers.SystemActorTest do
|> Ash.Query.filter(email == ^admin_email) |> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do |> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do
{:ok, user} when not is_nil(user) -> {:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts, actor: system_actor) delete_user_bypass_ash(user)
_ -> _ ->
:ok :ok
@ -275,7 +283,7 @@ defmodule Mv.Helpers.SystemActorTest do
|> Ash.Query.filter(email == ^"system@mila.local") |> Ash.Query.filter(email == ^"system@mila.local")
|> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do |> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do
{:ok, user} when not is_nil(user) -> {:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts, actor: system_actor) delete_user_bypass_ash(user)
_ -> _ ->
:ok :ok
@ -314,7 +322,7 @@ defmodule Mv.Helpers.SystemActorTest do
|> Ash.Query.filter(email == ^"system@mila.local") |> Ash.Query.filter(email == ^"system@mila.local")
|> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do |> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do
{:ok, user} when not is_nil(user) -> {:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts, actor: system_actor) delete_user_bypass_ash(user)
_ -> _ ->
:ok :ok
@ -328,7 +336,7 @@ defmodule Mv.Helpers.SystemActorTest do
|> Ash.Query.filter(email == ^admin_email) |> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do |> Ash.read_one(domain: Mv.Accounts, actor: system_actor) do
{:ok, user} when not is_nil(user) -> {:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts, actor: system_actor) delete_user_bypass_ash(user)
_ -> _ ->
:ok :ok

View file

@ -154,4 +154,14 @@ defmodule MvWeb.UserLive.ShowTest do
assert html =~ gettext("Show User") || html =~ to_string(user.email) assert html =~ gettext("Show User") || html =~ to_string(user.email)
end end
end end
describe "system actor user" do
test "redirects to user list when viewing system actor user", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn)
assert {:error, {:live_redirect, %{to: "/users"}}} =
live(conn, ~p"/users/#{system_actor.id}")
end
end
end end

View file

@ -420,4 +420,14 @@ defmodule MvWeb.UserLive.FormTest do
assert is_nil(updated_user.member) assert is_nil(updated_user.member)
end end
end end
describe "system actor user" do
test "redirects to user list when editing system actor user", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn, %{email: "admin@example.com"})
assert {:error, {:live_redirect, %{to: "/users"}}} =
live(conn, "/users/#{system_actor.id}/edit")
end
end
end end

View file

@ -405,6 +405,27 @@ defmodule MvWeb.UserLive.IndexTest do
end end
end end
describe "system actor user" do
test "does not show system actor user in list", %{conn: conn} do
# Ensure system actor exists (e.g. via get_system_actor in conn_with_oidc_user)
_system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_email = Mv.Helpers.SystemActor.system_user_email()
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, "/users")
refute html =~ system_email,
"System actor user (#{system_email}) must not appear in the user list"
end
test "destroying system actor user returns error", %{current_user: current_user} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
assert {:error, %Ash.Error.Invalid{}} =
Ash.destroy(system_actor, domain: Mv.Accounts, actor: current_user)
end
end
describe "member linking display" do describe "member linking display" do
test "displays linked member name in user list", %{conn: conn} do test "displays linked member name in user list", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor() system_actor = Mv.Helpers.SystemActor.get_system_actor()