mitgliederverwaltung/test/mv/helpers/system_actor_test.exs
Moritz 8eb05c8a6a
Document System Actor pattern in code guidelines
Add section explaining when and how to use system actor for systemic operations.
Include examples and distinction between user mode and system mode.
2026-01-27 10:14:04 +01:00

197 lines
6 KiB
Elixir

defmodule Mv.Helpers.SystemActorTest do
@moduledoc """
Tests for the SystemActor helper module.
"""
use Mv.DataCase, async: false
alias Mv.Helpers.SystemActor
alias Mv.Authorization
alias Mv.Accounts
require Ash.Query
setup do
# Ensure admin role exists
admin_role =
case Authorization.list_roles() do
{:ok, roles} ->
case Enum.find(roles, &(&1.permission_set_name == "admin")) do
nil ->
{:ok, role} =
Authorization.create_role(%{
name: "Admin",
description: "Administrator with full access",
permission_set_name: "admin"
})
role
role ->
role
end
_ ->
{:ok, role} =
Authorization.create_role(%{
name: "Admin",
description: "Administrator with full access",
permission_set_name: "admin"
})
role
end
# Ensure system user exists
system_user =
case Accounts.User
|> Ash.Query.filter(email == ^"system@mila.local")
|> Ash.read_one(domain: Mv.Accounts) do
{:ok, user} when not is_nil(user) ->
user
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!()
|> Ash.load!(:role, domain: Mv.Accounts)
_ ->
Accounts.create_user!(%{email: "system@mila.local"},
upsert?: true,
upsert_identity: :unique_email
)
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!()
|> Ash.load!(:role, domain: Mv.Accounts)
end
# Ensure admin user exists (for fallback)
admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost"
admin_user =
case Accounts.User
|> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts) do
{:ok, user} when not is_nil(user) ->
user
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!()
|> Ash.load!(:role, domain: Mv.Accounts)
_ ->
Accounts.create_user!(%{email: admin_email},
upsert?: true,
upsert_identity: :unique_email
)
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!()
|> Ash.load!(:role, domain: Mv.Accounts)
end
# Invalidate cache to ensure fresh load
SystemActor.invalidate_cache()
%{admin_role: admin_role, system_user: system_user, admin_user: admin_user}
end
describe "get_system_actor/0" do
test "returns system user with admin role", %{system_user: _system_user} do
system_actor = SystemActor.get_system_actor()
assert %Mv.Accounts.User{} = system_actor
assert to_string(system_actor.email) == "system@mila.local"
assert system_actor.role != nil
assert system_actor.role.permission_set_name == "admin"
end
test "falls back to admin user if system user doesn't exist", %{admin_user: _admin_user} do
# Delete system user if it exists
case Accounts.User
|> Ash.Query.filter(email == ^"system@mila.local")
|> Ash.read_one(domain: Mv.Accounts) do
{:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts)
_ ->
:ok
end
# Invalidate cache to force reload
SystemActor.invalidate_cache()
# Should fall back to admin user
system_actor = SystemActor.get_system_actor()
assert %Mv.Accounts.User{} = system_actor
assert system_actor.role != nil
assert system_actor.role.permission_set_name == "admin"
# Should be admin user, not system user
assert to_string(system_actor.email) != "system@mila.local"
end
test "caches system actor for performance" do
# First call
actor1 = SystemActor.get_system_actor()
# Second call should return cached actor (same struct)
actor2 = SystemActor.get_system_actor()
# Should be the same struct (cached)
assert actor1.id == actor2.id
end
test "creates system user in test environment if none exists", %{admin_role: _admin_role} do
# In test environment, system actor should auto-create if missing
# Delete all users to test auto-creation
case Accounts.User
|> Ash.Query.filter(email == ^"system@mila.local")
|> Ash.read_one(domain: Mv.Accounts) do
{:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts)
_ ->
:ok
end
admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost"
case Accounts.User
|> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts) do
{:ok, user} when not is_nil(user) ->
Ash.destroy!(user, domain: Mv.Accounts)
_ ->
:ok
end
# Invalidate cache
SystemActor.invalidate_cache()
# Should auto-create system user in test environment
system_actor = SystemActor.get_system_actor()
assert %Mv.Accounts.User{} = system_actor
assert to_string(system_actor.email) == "system@mila.local"
assert system_actor.role != nil
assert system_actor.role.permission_set_name == "admin"
end
end
describe "invalidate_cache/0" do
test "forces reload of system actor on next call" do
# Get initial actor
actor1 = SystemActor.get_system_actor()
# Invalidate cache
:ok = SystemActor.invalidate_cache()
# Next call should reload (but should be same user)
actor2 = SystemActor.get_system_actor()
# Should be same user (same ID)
assert actor1.id == actor2.id
end
end
end