defmodule Mv.Membership.CustomFieldPoliciesTest do @moduledoc """ Tests for CustomField resource authorization policies. Verifies that all authenticated users with a valid role can read custom fields, and only admin can create/update/destroy custom fields. """ use Mv.DataCase, async: false alias Mv.Membership.CustomField alias Mv.Accounts alias Mv.Authorization setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() %{actor: system_actor} end defp create_role_with_permission_set(permission_set_name, actor) do role_name = "Test Role #{permission_set_name} #{System.unique_integer([:positive])}" case Authorization.create_role( %{ name: role_name, description: "Test role for #{permission_set_name}", permission_set_name: permission_set_name }, actor: actor ) do {:ok, role} -> role {:error, error} -> raise "Failed to create role: #{inspect(error)}" end end defp create_user_with_permission_set(permission_set_name, actor) do role = create_role_with_permission_set(permission_set_name, actor) {:ok, user} = Accounts.User |> Ash.Changeset.for_create(:register_with_password, %{ email: "user#{System.unique_integer([:positive])}@example.com", password: "testpassword123" }) |> Ash.create(actor: actor) {:ok, user} = user |> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.manage_relationship(:role, role, type: :append_and_remove) |> Ash.update(actor: actor) {:ok, user_with_role} = Ash.load(user, :role, domain: Mv.Accounts, actor: actor) user_with_role end defp create_custom_field(actor) do {:ok, field} = CustomField |> Ash.Changeset.for_create(:create, %{ name: "test_field_#{System.unique_integer([:positive])}", value_type: :string }) |> Ash.create(actor: actor, domain: Mv.Membership) field end describe "read access (all roles)" do test "user with own_data can read all custom fields", %{actor: actor} do custom_field = create_custom_field(actor) user = create_user_with_permission_set("own_data", actor) {:ok, fields} = Ash.read(CustomField, actor: user, domain: Mv.Membership) ids = Enum.map(fields, & &1.id) assert custom_field.id in ids {:ok, fetched} = Ash.get(CustomField, custom_field.id, actor: user, domain: Mv.Membership) assert fetched.id == custom_field.id end test "user with read_only can read all custom fields", %{actor: actor} do custom_field = create_custom_field(actor) user = create_user_with_permission_set("read_only", actor) {:ok, fields} = Ash.read(CustomField, actor: user, domain: Mv.Membership) ids = Enum.map(fields, & &1.id) assert custom_field.id in ids {:ok, fetched} = Ash.get(CustomField, custom_field.id, actor: user, domain: Mv.Membership) assert fetched.id == custom_field.id end test "user with normal_user can read all custom fields", %{actor: actor} do custom_field = create_custom_field(actor) user = create_user_with_permission_set("normal_user", actor) {:ok, fields} = Ash.read(CustomField, actor: user, domain: Mv.Membership) ids = Enum.map(fields, & &1.id) assert custom_field.id in ids {:ok, fetched} = Ash.get(CustomField, custom_field.id, actor: user, domain: Mv.Membership) assert fetched.id == custom_field.id end test "user with admin can read all custom fields", %{actor: actor} do custom_field = create_custom_field(actor) user = create_user_with_permission_set("admin", actor) {:ok, fields} = Ash.read(CustomField, actor: user, domain: Mv.Membership) ids = Enum.map(fields, & &1.id) assert custom_field.id in ids {:ok, fetched} = Ash.get(CustomField, custom_field.id, actor: user, domain: Mv.Membership) assert fetched.id == custom_field.id end end describe "write access - non-admin cannot create/update/destroy" do setup %{actor: actor} do user = create_user_with_permission_set("normal_user", actor) custom_field = create_custom_field(actor) %{user: user, custom_field: custom_field} end test "non-admin cannot create custom field (forbidden)", %{user: user} do assert {:error, %Ash.Error.Forbidden{}} = CustomField |> Ash.Changeset.for_create(:create, %{ name: "forbidden_field_#{System.unique_integer([:positive])}", value_type: :string }) |> Ash.create(actor: user, domain: Mv.Membership) end test "non-admin cannot update custom field (forbidden)", %{ user: user, custom_field: custom_field } do assert {:error, %Ash.Error.Forbidden{}} = custom_field |> Ash.Changeset.for_update(:update, %{description: "Updated"}) |> Ash.update(actor: user, domain: Mv.Membership) end test "non-admin cannot destroy custom field (forbidden)", %{ user: user, custom_field: custom_field } do assert {:error, %Ash.Error.Forbidden{}} = Ash.destroy(custom_field, actor: user, domain: Mv.Membership) end end describe "write access - admin can create/update/destroy" do setup %{actor: actor} do user = create_user_with_permission_set("admin", actor) custom_field = create_custom_field(actor) %{user: user, custom_field: custom_field} end test "admin can create custom field", %{user: user} do name = "admin_field_#{System.unique_integer([:positive])}" assert {:ok, %CustomField{} = field} = CustomField |> Ash.Changeset.for_create(:create, %{name: name, value_type: :string}) |> Ash.create(actor: user, domain: Mv.Membership) assert field.name == name end test "admin can update custom field", %{user: user, custom_field: custom_field} do assert {:ok, updated} = custom_field |> Ash.Changeset.for_update(:update, %{description: "Admin updated"}) |> Ash.update(actor: user, domain: Mv.Membership) assert updated.description == "Admin updated" end test "admin can destroy custom field", %{user: user, custom_field: custom_field} do assert :ok = Ash.destroy(custom_field, actor: user, domain: Mv.Membership) assert {:error, _} = Ash.get(CustomField, custom_field.id, domain: Mv.Membership, actor: user) end end end