mitgliederverwaltung/test/mv/membership_fees/membership_fee_cycle_policies_test.exs
Moritz 178f5a01c7 MembershipFeeCycle: own_data read :linked via bypass and HasPermission scope
- own_data gets read scope :linked; apply_scope in HasPermission; bypass check for own_data.
- PermissionSetsTest expects own_data :linked, others :all for MFC read.
2026-02-04 09:20:10 +01:00

294 lines
8.9 KiB
Elixir

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