Add resource policies for Group, MemberGroup, MembershipFeeType, MembershipFeeCycle
- Group/MemberGroup/MembershipFeeType/MembershipFeeCycle: HasPermission policy - normal_user: Group and MembershipFeeCycle create/update/destroy; pages /groups/new, /groups/:slug/edit - Add policy tests for all four resources
This commit is contained in:
parent
893f9453bd
commit
5889683854
8 changed files with 1081 additions and 12 deletions
|
|
@ -36,7 +36,8 @@ defmodule Mv.Membership.Group do
|
||||||
"""
|
"""
|
||||||
use Ash.Resource,
|
use Ash.Resource,
|
||||||
domain: Mv.Membership,
|
domain: Mv.Membership,
|
||||||
data_layer: AshPostgres.DataLayer
|
data_layer: AshPostgres.DataLayer,
|
||||||
|
authorizers: [Ash.Policy.Authorizer]
|
||||||
|
|
||||||
require Ash.Query
|
require Ash.Query
|
||||||
alias Mv.Helpers
|
alias Mv.Helpers
|
||||||
|
|
@ -63,6 +64,13 @@ defmodule Mv.Membership.Group do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
policies do
|
||||||
|
policy action_type([:read, :create, :update, :destroy]) do
|
||||||
|
description "Check permissions from role (all can read; normal_user and admin can create/update/destroy)"
|
||||||
|
authorize_if Mv.Authorization.Checks.HasPermission
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
validations do
|
validations do
|
||||||
validate present(:name)
|
validate present(:name)
|
||||||
|
|
||||||
|
|
@ -136,7 +144,7 @@ defmodule Mv.Membership.Group do
|
||||||
query =
|
query =
|
||||||
Mv.Membership.Group
|
Mv.Membership.Group
|
||||||
|> Ash.Query.filter(fragment("LOWER(?) = LOWER(?)", name, ^name))
|
|> Ash.Query.filter(fragment("LOWER(?) = LOWER(?)", name, ^name))
|
||||||
|> maybe_exclude_id(exclude_id)
|
|> Helpers.query_exclude_id(exclude_id)
|
||||||
|
|
||||||
opts = Helpers.ash_actor_opts(actor)
|
opts = Helpers.ash_actor_opts(actor)
|
||||||
|
|
||||||
|
|
@ -155,7 +163,4 @@ defmodule Mv.Membership.Group do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_exclude_id(query, nil), do: query
|
|
||||||
defp maybe_exclude_id(query, id), do: Ash.Query.filter(query, id != ^id)
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,10 @@ defmodule Mv.Membership.MemberGroup do
|
||||||
"""
|
"""
|
||||||
use Ash.Resource,
|
use Ash.Resource,
|
||||||
domain: Mv.Membership,
|
domain: Mv.Membership,
|
||||||
data_layer: AshPostgres.DataLayer
|
data_layer: AshPostgres.DataLayer,
|
||||||
|
authorizers: [Ash.Policy.Authorizer]
|
||||||
|
|
||||||
|
import Ash.Expr
|
||||||
require Ash.Query
|
require Ash.Query
|
||||||
|
|
||||||
postgres do
|
postgres do
|
||||||
|
|
@ -56,6 +58,26 @@ defmodule Mv.Membership.MemberGroup do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Authorization: read uses bypass for :linked (own_data list) then HasPermission for :all;
|
||||||
|
# create/destroy use HasPermission (normal_user + admin only).
|
||||||
|
# Order: bypass first so own_data gets expr filter; HasPermission then authorizes :all for others.
|
||||||
|
policies do
|
||||||
|
bypass action_type(:read) do
|
||||||
|
description "own_data: read only member_groups where member_id == actor.member_id"
|
||||||
|
authorize_if expr(member_id == ^actor(:member_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
policy action_type(:read) do
|
||||||
|
description "Check read permission from role (read_only/normal_user/admin :all)"
|
||||||
|
authorize_if Mv.Authorization.Checks.HasPermission
|
||||||
|
end
|
||||||
|
|
||||||
|
policy action_type([:create, :destroy]) do
|
||||||
|
description "Check create/destroy from role (normal_user + admin only)"
|
||||||
|
authorize_if Mv.Authorization.Checks.HasPermission
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
validations do
|
validations do
|
||||||
validate present(:member_id)
|
validate present(:member_id)
|
||||||
validate present(:group_id)
|
validate present(:group_id)
|
||||||
|
|
@ -118,7 +140,7 @@ defmodule Mv.Membership.MemberGroup do
|
||||||
query =
|
query =
|
||||||
Mv.Membership.MemberGroup
|
Mv.Membership.MemberGroup
|
||||||
|> Ash.Query.filter(member_id == ^member_id and group_id == ^group_id)
|
|> Ash.Query.filter(member_id == ^member_id and group_id == ^group_id)
|
||||||
|> maybe_exclude_id(exclude_id)
|
|> Helpers.query_exclude_id(exclude_id)
|
||||||
|
|
||||||
opts = Helpers.ash_actor_opts(actor)
|
opts = Helpers.ash_actor_opts(actor)
|
||||||
|
|
||||||
|
|
@ -135,7 +157,4 @@ defmodule Mv.Membership.MemberGroup do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_exclude_id(query, nil), do: query
|
|
||||||
defp maybe_exclude_id(query, id), do: Ash.Query.filter(query, id != ^id)
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ defmodule Mv.MembershipFees.MembershipFeeCycle do
|
||||||
"""
|
"""
|
||||||
use Ash.Resource,
|
use Ash.Resource,
|
||||||
domain: Mv.MembershipFees,
|
domain: Mv.MembershipFees,
|
||||||
data_layer: AshPostgres.DataLayer
|
data_layer: AshPostgres.DataLayer,
|
||||||
|
authorizers: [Ash.Policy.Authorizer]
|
||||||
|
|
||||||
postgres do
|
postgres do
|
||||||
table "membership_fee_cycles"
|
table "membership_fee_cycles"
|
||||||
|
|
@ -83,6 +84,13 @@ defmodule Mv.MembershipFees.MembershipFeeCycle do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
policies do
|
||||||
|
policy action_type([:read, :create, :update, :destroy]) do
|
||||||
|
description "Check permissions from role (all read; normal_user and admin create/update/destroy)"
|
||||||
|
authorize_if Mv.Authorization.Checks.HasPermission
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
attributes do
|
attributes do
|
||||||
uuid_v7_primary_key :id
|
uuid_v7_primary_key :id
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@ defmodule Mv.MembershipFees.MembershipFeeType do
|
||||||
"""
|
"""
|
||||||
use Ash.Resource,
|
use Ash.Resource,
|
||||||
domain: Mv.MembershipFees,
|
domain: Mv.MembershipFees,
|
||||||
data_layer: AshPostgres.DataLayer
|
data_layer: AshPostgres.DataLayer,
|
||||||
|
authorizers: [Ash.Policy.Authorizer]
|
||||||
|
|
||||||
postgres do
|
postgres do
|
||||||
table "membership_fee_types"
|
table "membership_fee_types"
|
||||||
|
|
@ -61,6 +62,13 @@ defmodule Mv.MembershipFees.MembershipFeeType do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
policies do
|
||||||
|
policy action_type([:read, :create, :update, :destroy]) do
|
||||||
|
description "Check permissions from role (all can read, only admin can create/update/destroy)"
|
||||||
|
authorize_if Mv.Authorization.Checks.HasPermission
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
validations do
|
validations do
|
||||||
# Prevent interval changes after creation
|
# Prevent interval changes after creation
|
||||||
validate fn changeset, _context ->
|
validate fn changeset, _context ->
|
||||||
|
|
|
||||||
183
test/mv/membership/group_policies_test.exs
Normal file
183
test/mv/membership/group_policies_test.exs
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
defmodule Mv.Membership.GroupPoliciesTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests for Group resource authorization policies.
|
||||||
|
|
||||||
|
Verifies that own_data, read_only, normal_user can read groups;
|
||||||
|
only admin can create, update, and destroy groups.
|
||||||
|
"""
|
||||||
|
use Mv.DataCase, async: false
|
||||||
|
|
||||||
|
alias Mv.Membership
|
||||||
|
alias Mv.Accounts
|
||||||
|
alias Mv.Authorization
|
||||||
|
|
||||||
|
require Ash.Query
|
||||||
|
|
||||||
|
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_admin_user(actor) do
|
||||||
|
create_user_with_permission_set("admin", actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_group_fixture(actor) do
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
{:ok, group} =
|
||||||
|
Membership.create_group(
|
||||||
|
%{name: "Test Group #{System.unique_integer([:positive])}", description: "Test"},
|
||||||
|
actor: admin
|
||||||
|
)
|
||||||
|
|
||||||
|
group
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "own_data permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("own_data", actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
%{user: user, group: group}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read groups (list)", %{user: user} do
|
||||||
|
{:ok, groups} = Membership.list_groups(actor: user)
|
||||||
|
assert is_list(groups)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read single group", %{user: user, group: group} do
|
||||||
|
{:ok, found} = Ash.get(Membership.Group, group.id, actor: user, domain: Mv.Membership)
|
||||||
|
assert found.id == group.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "read_only permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("read_only", actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
%{user: user, group: group}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read groups (list)", %{user: user} do
|
||||||
|
{:ok, groups} = Membership.list_groups(actor: user)
|
||||||
|
assert is_list(groups)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read single group", %{user: user, group: group} do
|
||||||
|
{:ok, found} = Ash.get(Membership.Group, group.id, actor: user, domain: Mv.Membership)
|
||||||
|
assert found.id == group.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "normal_user permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("normal_user", actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
%{user: user, group: group}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read groups (list)", %{user: user} do
|
||||||
|
{:ok, groups} = Membership.list_groups(actor: user)
|
||||||
|
assert is_list(groups)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read single group", %{user: user, group: group} do
|
||||||
|
{:ok, found} = Ash.get(Membership.Group, group.id, actor: user, domain: Mv.Membership)
|
||||||
|
assert found.id == group.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can create group", %{user: user} do
|
||||||
|
assert {:ok, created} =
|
||||||
|
Membership.create_group(
|
||||||
|
%{name: "New Group #{System.unique_integer([:positive])}", description: "New"},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
|
||||||
|
assert created.name =~ "New Group"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can update group", %{user: user, group: group} do
|
||||||
|
assert {:ok, updated} =
|
||||||
|
Membership.update_group(group, %{description: "Updated"}, actor: user)
|
||||||
|
|
||||||
|
assert updated.description == "Updated"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can destroy group", %{user: user, group: group} do
|
||||||
|
assert :ok = Membership.destroy_group(group, actor: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "admin permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("admin", actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
%{user: user, group: group}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read groups (list)", %{user: user} do
|
||||||
|
{:ok, groups} = Membership.list_groups(actor: user)
|
||||||
|
assert is_list(groups)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can create group", %{user: user} do
|
||||||
|
name = "Admin Group #{System.unique_integer([:positive])}"
|
||||||
|
|
||||||
|
assert {:ok, group} =
|
||||||
|
Membership.create_group(%{name: name, description: "Admin created"}, actor: user)
|
||||||
|
|
||||||
|
assert group.name == name
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can update group", %{user: user, group: group} do
|
||||||
|
assert {:ok, updated} =
|
||||||
|
Membership.update_group(group, %{description: "Updated by admin"}, actor: user)
|
||||||
|
|
||||||
|
assert updated.description == "Updated by admin"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can destroy group", %{user: user, group: group} do
|
||||||
|
assert :ok = Membership.destroy_group(group, actor: user)
|
||||||
|
|
||||||
|
assert {:error, _} = Ash.get(Membership.Group, group.id, actor: user, domain: Mv.Membership)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
264
test/mv/membership/member_group_policies_test.exs
Normal file
264
test/mv/membership/member_group_policies_test.exs
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
defmodule Mv.Membership.MemberGroupPoliciesTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests for MemberGroup resource authorization policies.
|
||||||
|
|
||||||
|
Verifies own_data can only read linked member's associations;
|
||||||
|
read_only can read all, cannot create/destroy;
|
||||||
|
normal_user and admin can read, create, destroy.
|
||||||
|
"""
|
||||||
|
use Mv.DataCase, async: false
|
||||||
|
|
||||||
|
alias Mv.Membership
|
||||||
|
alias Mv.Accounts
|
||||||
|
alias Mv.Authorization
|
||||||
|
|
||||||
|
require Ash.Query
|
||||||
|
|
||||||
|
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_admin_user(actor) do
|
||||||
|
create_user_with_permission_set("admin", actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_member_fixture(actor) do
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
{: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_group_fixture(actor) do
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
{:ok, group} =
|
||||||
|
Membership.create_group(
|
||||||
|
%{name: "Test Group #{System.unique_integer([:positive])}", description: "Test"},
|
||||||
|
actor: admin
|
||||||
|
)
|
||||||
|
|
||||||
|
group
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_member_group_fixture(member_id, group_id, actor) do
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
{:ok, member_group} =
|
||||||
|
Membership.create_member_group(%{member_id: member_id, group_id: group_id}, actor: admin)
|
||||||
|
|
||||||
|
member_group
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "own_data permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("own_data", actor)
|
||||||
|
member = create_member_fixture(actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
# Link user to member so actor.member_id is set
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
user =
|
||||||
|
user
|
||||||
|
|> Ash.Changeset.for_update(:update, %{})
|
||||||
|
|> Ash.Changeset.force_change_attribute(:member_id, member.id)
|
||||||
|
|> Ash.update(actor: admin)
|
||||||
|
|
||||||
|
{:ok, user} = Ash.load(user, :role, domain: Mv.Accounts, actor: actor)
|
||||||
|
mg_linked = create_member_group_fixture(member.id, group.id, actor)
|
||||||
|
# MemberGroup for another member (not linked to user)
|
||||||
|
other_member = create_member_fixture(actor)
|
||||||
|
other_group = create_group_fixture(actor)
|
||||||
|
mg_other = create_member_group_fixture(other_member.id, other_group.id, actor)
|
||||||
|
%{user: user, member: member, group: group, mg_linked: mg_linked, mg_other: mg_other}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read member_groups for linked member only", %{user: user, mg_linked: mg_linked} do
|
||||||
|
{:ok, list} =
|
||||||
|
Mv.Membership.MemberGroup
|
||||||
|
|> Ash.read(actor: user, domain: Mv.Membership)
|
||||||
|
|
||||||
|
ids = Enum.map(list, & &1.id)
|
||||||
|
assert mg_linked.id in ids
|
||||||
|
refute Enum.empty?(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "list returns only member_groups where member_id == actor.member_id", %{
|
||||||
|
user: user,
|
||||||
|
mg_linked: mg_linked,
|
||||||
|
mg_other: mg_other
|
||||||
|
} do
|
||||||
|
{:ok, list} =
|
||||||
|
Mv.Membership.MemberGroup
|
||||||
|
|> Ash.read(actor: user, domain: Mv.Membership)
|
||||||
|
|
||||||
|
ids = Enum.map(list, & &1.id)
|
||||||
|
assert mg_linked.id in ids
|
||||||
|
refute mg_other.id in ids
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot create member_group (returns forbidden)", %{user: user, actor: actor} do
|
||||||
|
# Use fresh member/group so we assert on Forbidden, not on duplicate validation
|
||||||
|
other_member = create_member_fixture(actor)
|
||||||
|
other_group = create_group_fixture(actor)
|
||||||
|
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
Membership.create_member_group(
|
||||||
|
%{member_id: other_member.id, group_id: other_group.id},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot destroy member_group (returns forbidden)", %{user: user, mg_linked: mg_linked} do
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
Membership.destroy_member_group(mg_linked, actor: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "read_only permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("read_only", actor)
|
||||||
|
member = create_member_fixture(actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
mg = create_member_group_fixture(member.id, group.id, actor)
|
||||||
|
%{actor: actor, user: user, member: member, group: group, mg: mg}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read all member_groups", %{user: user, mg: mg} do
|
||||||
|
{:ok, list} =
|
||||||
|
Mv.Membership.MemberGroup
|
||||||
|
|> Ash.read(actor: user, domain: Mv.Membership)
|
||||||
|
|
||||||
|
ids = Enum.map(list, & &1.id)
|
||||||
|
assert mg.id in ids
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot create member_group (returns forbidden)", %{user: user, actor: actor} do
|
||||||
|
member = create_member_fixture(actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
Membership.create_member_group(%{member_id: member.id, group_id: group.id},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot destroy member_group (returns forbidden)", %{user: user, mg: mg} do
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
Membership.destroy_member_group(mg, actor: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "normal_user permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("normal_user", actor)
|
||||||
|
member = create_member_fixture(actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
mg = create_member_group_fixture(member.id, group.id, actor)
|
||||||
|
%{actor: actor, user: user, member: member, group: group, mg: mg}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read all member_groups", %{user: user, mg: mg} do
|
||||||
|
{:ok, list} =
|
||||||
|
Mv.Membership.MemberGroup
|
||||||
|
|> Ash.read(actor: user, domain: Mv.Membership)
|
||||||
|
|
||||||
|
ids = Enum.map(list, & &1.id)
|
||||||
|
assert mg.id in ids
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can create member_group", %{user: user, actor: actor} do
|
||||||
|
member = create_member_fixture(actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
|
||||||
|
assert {:ok, _mg} =
|
||||||
|
Membership.create_member_group(%{member_id: member.id, group_id: group.id},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can destroy member_group", %{user: user, mg: mg} do
|
||||||
|
assert :ok = Membership.destroy_member_group(mg, actor: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "admin permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("admin", actor)
|
||||||
|
member = create_member_fixture(actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
mg = create_member_group_fixture(member.id, group.id, actor)
|
||||||
|
%{actor: actor, user: user, member: member, group: group, mg: mg}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read all member_groups", %{user: user, mg: mg} do
|
||||||
|
{:ok, list} =
|
||||||
|
Mv.Membership.MemberGroup
|
||||||
|
|> Ash.read(actor: user, domain: Mv.Membership)
|
||||||
|
|
||||||
|
ids = Enum.map(list, & &1.id)
|
||||||
|
assert mg.id in ids
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can create member_group", %{user: user, actor: actor} do
|
||||||
|
member = create_member_fixture(actor)
|
||||||
|
group = create_group_fixture(actor)
|
||||||
|
|
||||||
|
assert {:ok, _mg} =
|
||||||
|
Membership.create_member_group(%{member_id: member.id, group_id: group.id},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can destroy member_group", %{user: user, mg: mg} do
|
||||||
|
assert :ok = Membership.destroy_member_group(mg, actor: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
279
test/mv/membership_fees/membership_fee_cycle_policies_test.exs
Normal file
279
test/mv/membership_fees/membership_fee_cycle_policies_test.exs
Normal file
|
|
@ -0,0 +1,279 @@
|
||||||
|
defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests for MembershipFeeCycle resource authorization policies.
|
||||||
|
|
||||||
|
Verifies read_only can only read (no update/mark_as_paid);
|
||||||
|
normal_user and admin can read and update (including mark_as_paid);
|
||||||
|
only admin can create and destroy.
|
||||||
|
"""
|
||||||
|
use Mv.DataCase, async: false
|
||||||
|
|
||||||
|
alias Mv.MembershipFees
|
||||||
|
alias Mv.Membership
|
||||||
|
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_admin_user(actor) do
|
||||||
|
create_user_with_permission_set("admin", actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_member_fixture(actor) do
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
{: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(actor) do
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
{: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(actor) do
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
member = create_member_fixture(actor)
|
||||||
|
fee_type = create_fee_type_fixture(actor)
|
||||||
|
|
||||||
|
{: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 "read_only permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("read_only", actor)
|
||||||
|
cycle = create_cycle_fixture(actor)
|
||||||
|
%{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(actor)
|
||||||
|
fee_type = create_fee_type_fixture(actor)
|
||||||
|
|
||||||
|
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 = create_user_with_permission_set("normal_user", actor)
|
||||||
|
cycle = create_cycle_fixture(actor)
|
||||||
|
%{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(actor)
|
||||||
|
fee_type = create_fee_type_fixture(actor)
|
||||||
|
|
||||||
|
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 = create_user_with_permission_set("admin", actor)
|
||||||
|
cycle = create_cycle_fixture(actor)
|
||||||
|
%{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(actor)
|
||||||
|
fee_type = create_fee_type_fixture(actor)
|
||||||
|
|
||||||
|
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
|
||||||
303
test/mv/membership_fees/membership_fee_type_policies_test.exs
Normal file
303
test/mv/membership_fees/membership_fee_type_policies_test.exs
Normal file
|
|
@ -0,0 +1,303 @@
|
||||||
|
defmodule Mv.MembershipFees.MembershipFeeTypePoliciesTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests for MembershipFeeType resource authorization policies.
|
||||||
|
|
||||||
|
Verifies all roles (own_data, read_only, normal_user, admin) can read;
|
||||||
|
only admin can create, update, and destroy; non-admin create/update/destroy → Forbidden.
|
||||||
|
"""
|
||||||
|
use Mv.DataCase, async: false
|
||||||
|
|
||||||
|
alias Mv.MembershipFees
|
||||||
|
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_admin_user(actor) do
|
||||||
|
create_user_with_permission_set("admin", actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_membership_fee_type_fixture(actor) do
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
{: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
|
||||||
|
|
||||||
|
describe "own_data permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("own_data", actor)
|
||||||
|
fee_type = create_membership_fee_type_fixture(actor)
|
||||||
|
%{actor: actor, user: user, fee_type: fee_type}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read membership_fee_types (list)", %{user: user} do
|
||||||
|
{:ok, list} =
|
||||||
|
Mv.MembershipFees.MembershipFeeType
|
||||||
|
|> Ash.read(actor: user, domain: Mv.MembershipFees)
|
||||||
|
|
||||||
|
assert is_list(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read single membership_fee_type", %{user: user, fee_type: fee_type} do
|
||||||
|
{:ok, found} =
|
||||||
|
Ash.get(Mv.MembershipFees.MembershipFeeType, fee_type.id,
|
||||||
|
actor: user,
|
||||||
|
domain: Mv.MembershipFees
|
||||||
|
)
|
||||||
|
|
||||||
|
assert found.id == fee_type.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot create membership_fee_type (returns forbidden)", %{user: user} do
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
MembershipFees.create_membership_fee_type(
|
||||||
|
%{
|
||||||
|
name: "New Fee #{System.unique_integer([:positive])}",
|
||||||
|
amount: Decimal.new("5.00"),
|
||||||
|
interval: :monthly
|
||||||
|
},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot update membership_fee_type (returns forbidden)", %{
|
||||||
|
user: user,
|
||||||
|
fee_type: fee_type
|
||||||
|
} do
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
MembershipFees.update_membership_fee_type(fee_type, %{name: "Updated"},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot destroy membership_fee_type (returns forbidden)", %{user: user, actor: actor} do
|
||||||
|
# Use a fee type with no members/cycles so destroy would succeed if authorized
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
{:ok, isolated} =
|
||||||
|
MembershipFees.create_membership_fee_type(
|
||||||
|
%{
|
||||||
|
name: "Isolated #{System.unique_integer([:positive])}",
|
||||||
|
amount: Decimal.new("1.00"),
|
||||||
|
interval: :yearly
|
||||||
|
},
|
||||||
|
actor: admin
|
||||||
|
)
|
||||||
|
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
MembershipFees.destroy_membership_fee_type(isolated, actor: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "read_only permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("read_only", actor)
|
||||||
|
fee_type = create_membership_fee_type_fixture(actor)
|
||||||
|
%{actor: actor, user: user, fee_type: fee_type}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read membership_fee_types (list)", %{user: user} do
|
||||||
|
{:ok, list} =
|
||||||
|
Mv.MembershipFees.MembershipFeeType
|
||||||
|
|> Ash.read(actor: user, domain: Mv.MembershipFees)
|
||||||
|
|
||||||
|
assert is_list(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot create membership_fee_type (returns forbidden)", %{user: user} do
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
MembershipFees.create_membership_fee_type(
|
||||||
|
%{
|
||||||
|
name: "New Fee #{System.unique_integer([:positive])}",
|
||||||
|
amount: Decimal.new("5.00"),
|
||||||
|
interval: :monthly
|
||||||
|
},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot update membership_fee_type (returns forbidden)", %{
|
||||||
|
user: user,
|
||||||
|
fee_type: fee_type
|
||||||
|
} do
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
MembershipFees.update_membership_fee_type(fee_type, %{name: "Updated"},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot destroy membership_fee_type (returns forbidden)", %{user: user, actor: actor} do
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
{:ok, isolated} =
|
||||||
|
MembershipFees.create_membership_fee_type(
|
||||||
|
%{
|
||||||
|
name: "Isolated #{System.unique_integer([:positive])}",
|
||||||
|
amount: Decimal.new("1.00"),
|
||||||
|
interval: :yearly
|
||||||
|
},
|
||||||
|
actor: admin
|
||||||
|
)
|
||||||
|
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
MembershipFees.destroy_membership_fee_type(isolated, actor: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "normal_user permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("normal_user", actor)
|
||||||
|
fee_type = create_membership_fee_type_fixture(actor)
|
||||||
|
%{actor: actor, user: user, fee_type: fee_type}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read membership_fee_types (list)", %{user: user} do
|
||||||
|
{:ok, list} =
|
||||||
|
Mv.MembershipFees.MembershipFeeType
|
||||||
|
|> Ash.read(actor: user, domain: Mv.MembershipFees)
|
||||||
|
|
||||||
|
assert is_list(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot create membership_fee_type (returns forbidden)", %{user: user} do
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
MembershipFees.create_membership_fee_type(
|
||||||
|
%{
|
||||||
|
name: "New Fee #{System.unique_integer([:positive])}",
|
||||||
|
amount: Decimal.new("5.00"),
|
||||||
|
interval: :monthly
|
||||||
|
},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot update membership_fee_type (returns forbidden)", %{
|
||||||
|
user: user,
|
||||||
|
fee_type: fee_type
|
||||||
|
} do
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
MembershipFees.update_membership_fee_type(fee_type, %{name: "Updated"},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot destroy membership_fee_type (returns forbidden)", %{user: user, actor: actor} do
|
||||||
|
admin = create_admin_user(actor)
|
||||||
|
|
||||||
|
{:ok, isolated} =
|
||||||
|
MembershipFees.create_membership_fee_type(
|
||||||
|
%{
|
||||||
|
name: "Isolated #{System.unique_integer([:positive])}",
|
||||||
|
amount: Decimal.new("1.00"),
|
||||||
|
interval: :yearly
|
||||||
|
},
|
||||||
|
actor: admin
|
||||||
|
)
|
||||||
|
|
||||||
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
|
MembershipFees.destroy_membership_fee_type(isolated, actor: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "admin permission set" do
|
||||||
|
setup %{actor: actor} do
|
||||||
|
user = create_user_with_permission_set("admin", actor)
|
||||||
|
fee_type = create_membership_fee_type_fixture(actor)
|
||||||
|
%{actor: actor, user: user, fee_type: fee_type}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can read membership_fee_types (list)", %{user: user} do
|
||||||
|
{:ok, list} =
|
||||||
|
Mv.MembershipFees.MembershipFeeType
|
||||||
|
|> Ash.read(actor: user, domain: Mv.MembershipFees)
|
||||||
|
|
||||||
|
assert is_list(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can create membership_fee_type", %{user: user} do
|
||||||
|
name = "Admin Fee #{System.unique_integer([:positive])}"
|
||||||
|
|
||||||
|
assert {:ok, created} =
|
||||||
|
MembershipFees.create_membership_fee_type(
|
||||||
|
%{name: name, amount: Decimal.new("20.00"), interval: :quarterly},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
|
||||||
|
assert created.name == name
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can update membership_fee_type", %{user: user, fee_type: fee_type} do
|
||||||
|
new_name = "Updated #{System.unique_integer([:positive])}"
|
||||||
|
|
||||||
|
assert {:ok, updated} =
|
||||||
|
MembershipFees.update_membership_fee_type(fee_type, %{name: new_name}, actor: user)
|
||||||
|
|
||||||
|
assert updated.name == new_name
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can destroy membership_fee_type", %{user: user} do
|
||||||
|
{:ok, isolated} =
|
||||||
|
MembershipFees.create_membership_fee_type(
|
||||||
|
%{
|
||||||
|
name: "To Delete #{System.unique_integer([:positive])}",
|
||||||
|
amount: Decimal.new("1.00"),
|
||||||
|
interval: :yearly
|
||||||
|
},
|
||||||
|
actor: user
|
||||||
|
)
|
||||||
|
|
||||||
|
assert :ok = MembershipFees.destroy_membership_fee_type(isolated, actor: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue