defmodule Mv.Fixtures do @moduledoc """ Shared test fixtures for consistent test data creation. This module provides factory functions for creating test data across different test suites, ensuring consistency and reducing duplication. """ @doc """ Creates a member with default or custom attributes. ## Parameters - `attrs` - Map or keyword list of attributes to override defaults ## Returns - Member struct ## Examples iex> member_fixture() %Mv.Membership.Member{first_name: "Test", ...} iex> member_fixture(%{first_name: "Alice", email: "alice@example.com"}) %Mv.Membership.Member{first_name: "Alice", email: "alice@example.com"} """ def member_fixture(attrs \\ %{}) do attrs |> Enum.into(%{ first_name: "Test", last_name: "Member", email: "test#{System.unique_integer([:positive])}@example.com" }) |> Mv.Membership.create_member() |> case do {:ok, member} -> member {:error, error} -> raise "Failed to create member: #{inspect(error)}" end end @doc """ Creates a user with default or custom attributes. ## Parameters - `attrs` - Map or keyword list of attributes to override defaults ## Returns - User struct ## Examples iex> user_fixture() %Mv.Accounts.User{email: "user123@example.com"} iex> user_fixture(%{email: "custom@example.com"}) %Mv.Accounts.User{email: "custom@example.com"} """ def user_fixture(attrs \\ %{}) do attrs |> Enum.into(%{ email: "user#{System.unique_integer([:positive])}@example.com" }) |> Mv.Accounts.create_user() |> case do {:ok, user} -> user {:error, error} -> raise "Failed to create user: #{inspect(error)}" end end @doc """ Creates a user linked to a member. ## Parameters - `user_attrs` - Map or keyword list of user attributes - `member_attrs` - Map or keyword list of member attributes ## Returns - Tuple of {user, member} ## Examples iex> {user, member} = linked_user_member_fixture() iex> user.member_id == member.id true """ def linked_user_member_fixture(user_attrs \\ %{}, member_attrs \\ %{}) do member = member_fixture(member_attrs) user_attrs = Map.put(user_attrs, :member, %{id: member.id}) user = user_fixture(user_attrs) {user, member} end @doc """ Creates a role with a specific permission set. ## Parameters - `permission_set_name` - The permission set name (e.g., "admin", "read_only", "normal_user", "own_data") ## Returns - Role struct ## Examples iex> role_fixture("admin") %Mv.Authorization.Role{permission_set_name: "admin", ...} """ def role_fixture(permission_set_name) do role_name = "Test Role #{permission_set_name} #{System.unique_integer([:positive])}" case Mv.Authorization.create_role(%{ name: role_name, description: "Test role for #{permission_set_name}", permission_set_name: permission_set_name }) do {:ok, role} -> role {:error, error} -> raise "Failed to create role: #{inspect(error)}" end end @doc """ Creates a user with a specific permission set (role). ## Parameters - `permission_set_name` - The permission set name (e.g., "admin", "read_only", "normal_user", "own_data") - `user_attrs` - Optional user attributes ## Returns - User struct with role preloaded ## Examples iex> admin_user = user_with_role_fixture("admin") iex> admin_user.role.permission_set_name "admin" """ def user_with_role_fixture(permission_set_name \\ "admin", user_attrs \\ %{}) do # Create role with permission set role = role_fixture(permission_set_name) # Create user {:ok, user} = user_attrs |> Enum.into(%{ email: "user#{System.unique_integer([:positive])}@example.com" }) |> Mv.Accounts.create_user() # Assign role to user {:ok, user} = user |> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.manage_relationship(:role, role, type: :append_and_remove) |> Ash.update() # Reload user with role preloaded (critical for authorization!) {:ok, user_with_role} = Ash.load(user, :role, domain: Mv.Accounts) user_with_role end @doc """ Creates a member with an actor (for use in tests with policies). ## Parameters - `attrs` - Map or keyword list of attributes to override defaults - `actor` - The actor (user) to use for authorization ## Returns - Member struct ## Examples iex> admin = user_with_role_fixture("admin") iex> member_fixture_with_actor(%{first_name: "Alice"}, admin) %Mv.Membership.Member{first_name: "Alice", ...} """ def member_fixture_with_actor(attrs \\ %{}, actor) do attrs |> Enum.into(%{ first_name: "Test", last_name: "Member", email: "test#{System.unique_integer([:positive])}@example.com" }) |> Mv.Membership.create_member(actor: actor) |> case do {:ok, member} -> member {:error, error} -> raise "Failed to create member: #{inspect(error)}" end end end