defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do @moduledoc """ Tests for MembershipFeeCycle resource authorization policies. Verifies own_data can only read :linked (linked member's cycles); read_only can only read (no create/update/destroy); normal_user and admin can read, create, update, destroy (including mark_as_paid). """ use Mv.DataCase, async: false alias Mv.MembershipFees alias Mv.Membership setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() %{actor: system_actor} end defp create_member_fixture do admin = Mv.Fixtures.user_with_role_fixture("admin") {:ok, member} = Membership.create_member( %{ first_name: "Test", last_name: "Member", email: "test#{System.unique_integer([:positive])}@example.com" }, actor: admin ) member end defp create_fee_type_fixture do admin = Mv.Fixtures.user_with_role_fixture("admin") {:ok, fee_type} = MembershipFees.create_membership_fee_type( %{ name: "Test Fee #{System.unique_integer([:positive])}", amount: Decimal.new("10.00"), interval: :yearly, description: "Test" }, actor: admin ) fee_type end defp create_cycle_fixture do admin = Mv.Fixtures.user_with_role_fixture("admin") member = create_member_fixture() fee_type = create_fee_type_fixture() {:ok, cycle} = MembershipFees.create_membership_fee_cycle( %{ member_id: member.id, membership_fee_type_id: fee_type.id, cycle_start: Date.utc_today(), amount: Decimal.new("10.00"), status: :unpaid }, actor: admin ) cycle end describe "own_data permission set" do setup %{actor: actor} do user = Mv.Fixtures.user_with_role_fixture("own_data") linked_member = create_member_fixture() other_member = create_member_fixture() fee_type = create_fee_type_fixture() admin = Mv.Fixtures.user_with_role_fixture("admin") user = user |> Ash.Changeset.for_update(:update, %{}, domain: Mv.Accounts) |> Ash.Changeset.force_change_attribute(:member_id, linked_member.id) |> Ash.update(actor: admin, domain: Mv.Accounts) {:ok, user} = Ash.load(user, :role, domain: Mv.Accounts, actor: actor) {:ok, cycle_linked} = MembershipFees.create_membership_fee_cycle( %{ member_id: linked_member.id, membership_fee_type_id: fee_type.id, cycle_start: Date.utc_today(), amount: Decimal.new("10.00"), status: :unpaid }, actor: admin ) {:ok, cycle_other} = MembershipFees.create_membership_fee_cycle( %{ member_id: other_member.id, membership_fee_type_id: fee_type.id, cycle_start: Date.add(Date.utc_today(), -365), amount: Decimal.new("10.00"), status: :unpaid }, actor: admin ) %{user: user, cycle_linked: cycle_linked, cycle_other: cycle_other} end test "can read only linked member's cycles", %{ user: user, cycle_linked: cycle_linked, cycle_other: cycle_other } do {:ok, list} = Mv.MembershipFees.MembershipFeeCycle |> Ash.read(actor: user, domain: Mv.MembershipFees) ids = Enum.map(list, & &1.id) assert cycle_linked.id in ids refute cycle_other.id in ids end end describe "read_only permission set" do setup %{actor: actor} do user = Mv.Fixtures.user_with_role_fixture("read_only") cycle = create_cycle_fixture() %{actor: actor, user: user, cycle: cycle} end test "can read membership_fee_cycles (list)", %{user: user} do {:ok, list} = Mv.MembershipFees.MembershipFeeCycle |> Ash.read(actor: user, domain: Mv.MembershipFees) assert is_list(list) end test "cannot update cycle (returns forbidden)", %{user: user, cycle: cycle} do assert {:error, %Ash.Error.Forbidden{}} = MembershipFees.update_membership_fee_cycle(cycle, %{status: :paid}, actor: user) end test "cannot mark_as_paid (returns forbidden)", %{user: user, cycle: cycle} do assert {:error, %Ash.Error.Forbidden{}} = cycle |> Ash.Changeset.for_update(:mark_as_paid, %{}, domain: Mv.MembershipFees) |> Ash.update(actor: user, domain: Mv.MembershipFees) end test "cannot create cycle (returns forbidden)", %{user: user, actor: _actor} do member = create_member_fixture() fee_type = create_fee_type_fixture() assert {:error, %Ash.Error.Forbidden{}} = MembershipFees.create_membership_fee_cycle( %{ member_id: member.id, membership_fee_type_id: fee_type.id, cycle_start: Date.utc_today(), amount: Decimal.new("10.00"), status: :unpaid }, actor: user ) end test "cannot destroy cycle (returns forbidden)", %{user: user, cycle: cycle} do assert {:error, %Ash.Error.Forbidden{}} = MembershipFees.destroy_membership_fee_cycle(cycle, actor: user) end end describe "normal_user permission set" do setup %{actor: actor} do user = Mv.Fixtures.user_with_role_fixture("normal_user") cycle = create_cycle_fixture() %{actor: actor, user: user, cycle: cycle} end test "can read membership_fee_cycles (list)", %{user: user} do {:ok, list} = Mv.MembershipFees.MembershipFeeCycle |> Ash.read(actor: user, domain: Mv.MembershipFees) assert is_list(list) end test "can update cycle status", %{user: user, cycle: cycle} do assert {:ok, updated} = MembershipFees.update_membership_fee_cycle(cycle, %{status: :paid}, actor: user) assert updated.status == :paid end test "can mark_as_paid", %{user: user, cycle: cycle} do assert {:ok, updated} = cycle |> Ash.Changeset.for_update(:mark_as_paid, %{}, domain: Mv.MembershipFees) |> Ash.update(actor: user, domain: Mv.MembershipFees) assert updated.status == :paid end test "can create cycle", %{user: user, actor: _actor} do member = create_member_fixture() fee_type = create_fee_type_fixture() assert {:ok, created} = MembershipFees.create_membership_fee_cycle( %{ member_id: member.id, membership_fee_type_id: fee_type.id, cycle_start: Date.utc_today(), amount: Decimal.new("10.00"), status: :unpaid }, actor: user ) assert created.member_id == member.id end test "can destroy cycle", %{user: user, cycle: cycle} do assert :ok = MembershipFees.destroy_membership_fee_cycle(cycle, actor: user) end end describe "admin permission set" do setup %{actor: actor} do user = Mv.Fixtures.user_with_role_fixture("admin") cycle = create_cycle_fixture() %{actor: actor, user: user, cycle: cycle} end test "can read membership_fee_cycles (list)", %{user: user} do {:ok, list} = Mv.MembershipFees.MembershipFeeCycle |> Ash.read(actor: user, domain: Mv.MembershipFees) assert is_list(list) end test "can update cycle", %{user: user, cycle: cycle} do assert {:ok, updated} = MembershipFees.update_membership_fee_cycle(cycle, %{status: :paid}, actor: user) assert updated.status == :paid end test "can mark_as_paid", %{user: user, cycle: cycle} do cycle_unpaid = cycle |> Ash.Changeset.for_update(:mark_as_unpaid, %{}, domain: Mv.MembershipFees) |> Ash.update!(actor: user, domain: Mv.MembershipFees) assert {:ok, updated} = cycle_unpaid |> Ash.Changeset.for_update(:mark_as_paid, %{}, domain: Mv.MembershipFees) |> Ash.update(actor: user, domain: Mv.MembershipFees) assert updated.status == :paid end test "can create cycle", %{user: user, actor: _actor} do member = create_member_fixture() fee_type = create_fee_type_fixture() assert {:ok, created} = MembershipFees.create_membership_fee_cycle( %{ member_id: member.id, membership_fee_type_id: fee_type.id, cycle_start: Date.utc_today(), amount: Decimal.new("10.00"), status: :unpaid }, actor: user ) assert created.member_id == member.id end test "can destroy cycle", %{user: user, cycle: cycle} do assert :ok = MembershipFees.destroy_membership_fee_cycle(cycle, actor: user) end end end