From f1bb6a0f9af9fca5163a7cdb0c602b6ad3b2f799 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 20 Jan 2026 22:09:21 +0100 Subject: [PATCH] Add tests for System Actor helper Test system actor retrieval, caching, fallback behavior, and auto-creation in test environment. --- test/mv/helpers/system_actor_test.exs | 193 ++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 test/mv/helpers/system_actor_test.exs diff --git a/test/mv/helpers/system_actor_test.exs b/test/mv/helpers/system_actor_test.exs new file mode 100644 index 0000000..2cf6b7f --- /dev/null +++ b/test/mv/helpers/system_actor_test.exs @@ -0,0 +1,193 @@ +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