defmodule Mv.Authorization.PermissionSetsTest do @moduledoc """ Tests for the PermissionSets module that defines hardcoded permission sets. """ use ExUnit.Case, async: true alias Mv.Authorization.PermissionSets describe "all_permission_sets/0" do test "returns all four permission sets" do sets = PermissionSets.all_permission_sets() assert length(sets) == 4 assert :own_data in sets assert :read_only in sets assert :normal_user in sets assert :admin in sets end end describe "get_permissions/1" do test "all permission sets return map with :resources and :pages keys" do for set <- PermissionSets.all_permission_sets() do permissions = PermissionSets.get_permissions(set) assert Map.has_key?(permissions, :resources), "#{set} missing :resources key" assert Map.has_key?(permissions, :pages), "#{set} missing :pages key" assert is_list(permissions.resources), "#{set} :resources must be a list" assert is_list(permissions.pages), "#{set} :pages must be a list" end end test "each resource permission has required keys" do permissions = PermissionSets.get_permissions(:own_data) Enum.each(permissions.resources, fn perm -> assert Map.has_key?(perm, :resource) assert Map.has_key?(perm, :action) assert Map.has_key?(perm, :scope) assert Map.has_key?(perm, :granted) assert is_binary(perm.resource) assert perm.action in [:read, :create, :update, :destroy] assert perm.scope in [:own, :linked, :all] assert is_boolean(perm.granted) end) end test "pages lists are non-empty for all permission sets" do for set <- [:own_data, :read_only, :normal_user, :admin] do permissions = PermissionSets.get_permissions(set) assert not Enum.empty?(permissions.pages), "Permission set #{set} should have at least one page" end end end describe "get_permissions/1 - :own_data permission content" do test "allows User read/update with scope :own" do permissions = PermissionSets.get_permissions(:own_data) user_read = Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :read end) user_update = Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :update end) assert user_read.scope == :own assert user_read.granted == true assert user_update.scope == :own assert user_update.granted == true end test "allows Member read/update with scope :linked" do permissions = PermissionSets.get_permissions(:own_data) member_read = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :read end) member_update = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :update end) assert member_read.scope == :linked assert member_read.granted == true assert member_update.scope == :linked assert member_update.granted == true end test "allows CustomFieldValue read/update with scope :linked" do permissions = PermissionSets.get_permissions(:own_data) cfv_read = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :read end) cfv_update = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :update end) assert cfv_read.scope == :linked assert cfv_read.granted == true assert cfv_update.scope == :linked assert cfv_update.granted == true end test "allows CustomField read with scope :all" do permissions = PermissionSets.get_permissions(:own_data) cf_read = Enum.find(permissions.resources, fn p -> p.resource == "CustomField" && p.action == :read end) assert cf_read.scope == :all assert cf_read.granted == true end test "includes correct pages" do permissions = PermissionSets.get_permissions(:own_data) # Root "/" is not allowed for own_data (Mitglied is redirected to profile) refute "/" in permissions.pages # Profile is at /users/:id, not a separate /profile route assert "/users/:id" in permissions.pages assert "/members/:id" in permissions.pages end end describe "get_permissions/1 - :read_only permission content" do test "allows User read/update with scope :own" do permissions = PermissionSets.get_permissions(:read_only) user_read = Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :read end) user_update = Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :update end) assert user_read.scope == :own assert user_read.granted == true assert user_update.scope == :own assert user_update.granted == true end test "allows Member read with scope :all" do permissions = PermissionSets.get_permissions(:read_only) member_read = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :read end) assert member_read.scope == :all assert member_read.granted == true end test "does NOT allow Member create/update/destroy" do permissions = PermissionSets.get_permissions(:read_only) member_create = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :create end) member_update = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :update end) member_destroy = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :destroy end) assert member_create == nil || member_create.granted == false assert member_update == nil || member_update.granted == false assert member_destroy == nil || member_destroy.granted == false end test "allows CustomFieldValue read with scope :all" do permissions = PermissionSets.get_permissions(:read_only) cfv_read = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :read end) assert cfv_read.scope == :all assert cfv_read.granted == true end test "does NOT allow CustomFieldValue create/update/destroy" do permissions = PermissionSets.get_permissions(:read_only) cfv_create = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :create end) cfv_update = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :update end) cfv_destroy = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :destroy end) assert cfv_create == nil || cfv_create.granted == false assert cfv_update == nil || cfv_update.granted == false assert cfv_destroy == nil || cfv_destroy.granted == false end test "allows CustomField read with scope :all" do permissions = PermissionSets.get_permissions(:read_only) cf_read = Enum.find(permissions.resources, fn p -> p.resource == "CustomField" && p.action == :read end) assert cf_read.scope == :all assert cf_read.granted == true end test "includes correct pages" do permissions = PermissionSets.get_permissions(:read_only) assert "/" in permissions.pages assert "/users/:id" in permissions.pages assert "/members" in permissions.pages assert "/members/:id" in permissions.pages assert "/custom_field_values" in permissions.pages assert "/custom_field_values/:id" in permissions.pages end end describe "get_permissions/1 - :normal_user permission content" do test "allows User read/update with scope :own" do permissions = PermissionSets.get_permissions(:normal_user) user_read = Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :read end) user_update = Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :update end) assert user_read.scope == :own assert user_read.granted == true assert user_update.scope == :own assert user_update.granted == true end test "allows Member read/create/update with scope :all" do permissions = PermissionSets.get_permissions(:normal_user) member_read = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :read end) member_create = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :create end) member_update = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :update end) assert member_read.scope == :all assert member_read.granted == true assert member_create.scope == :all assert member_create.granted == true assert member_update.scope == :all assert member_update.granted == true end test "does NOT allow Member destroy (safety)" do permissions = PermissionSets.get_permissions(:normal_user) member_destroy = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :destroy end) assert member_destroy == nil || member_destroy.granted == false end test "allows CustomFieldValue full CRUD with scope :all" do permissions = PermissionSets.get_permissions(:normal_user) cfv_read = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :read end) cfv_create = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :create end) cfv_update = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :update end) cfv_destroy = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :destroy end) assert cfv_read.scope == :all assert cfv_read.granted == true assert cfv_create.scope == :all assert cfv_create.granted == true assert cfv_update.scope == :all assert cfv_update.granted == true assert cfv_destroy.scope == :all assert cfv_destroy.granted == true end test "allows CustomField read with scope :all" do permissions = PermissionSets.get_permissions(:normal_user) cf_read = Enum.find(permissions.resources, fn p -> p.resource == "CustomField" && p.action == :read end) assert cf_read.scope == :all assert cf_read.granted == true end test "includes correct pages" do permissions = PermissionSets.get_permissions(:normal_user) assert "/" in permissions.pages assert "/users/:id" in permissions.pages assert "/members" in permissions.pages assert "/members/new" in permissions.pages assert "/members/:id" in permissions.pages assert "/members/:id/edit" in permissions.pages assert "/custom_field_values" in permissions.pages assert "/custom_field_values/:id" in permissions.pages assert "/custom_field_values/new" in permissions.pages assert "/custom_field_values/:id/edit" in permissions.pages end end describe "get_permissions/1 - :admin permission content" do test "allows User full CRUD with scope :all" do permissions = PermissionSets.get_permissions(:admin) user_read = Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :read end) user_create = Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :create end) user_update = Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :update end) user_destroy = Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :destroy end) assert user_read.scope == :all assert user_read.granted == true assert user_create.scope == :all assert user_create.granted == true assert user_update.scope == :all assert user_update.granted == true assert user_destroy.scope == :all assert user_destroy.granted == true end test "allows Member full CRUD with scope :all" do permissions = PermissionSets.get_permissions(:admin) member_read = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :read end) member_create = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :create end) member_update = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :update end) member_destroy = Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :destroy end) assert member_read.scope == :all assert member_read.granted == true assert member_create.scope == :all assert member_create.granted == true assert member_update.scope == :all assert member_update.granted == true assert member_destroy.scope == :all assert member_destroy.granted == true end test "allows CustomFieldValue full CRUD with scope :all" do permissions = PermissionSets.get_permissions(:admin) cfv_read = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :read end) cfv_create = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :create end) cfv_update = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :update end) cfv_destroy = Enum.find(permissions.resources, fn p -> p.resource == "CustomFieldValue" && p.action == :destroy end) assert cfv_read.scope == :all assert cfv_read.granted == true assert cfv_create.scope == :all assert cfv_create.granted == true assert cfv_update.scope == :all assert cfv_update.granted == true assert cfv_destroy.scope == :all assert cfv_destroy.granted == true end test "allows CustomField full CRUD with scope :all" do permissions = PermissionSets.get_permissions(:admin) cf_read = Enum.find(permissions.resources, fn p -> p.resource == "CustomField" && p.action == :read end) cf_create = Enum.find(permissions.resources, fn p -> p.resource == "CustomField" && p.action == :create end) cf_update = Enum.find(permissions.resources, fn p -> p.resource == "CustomField" && p.action == :update end) cf_destroy = Enum.find(permissions.resources, fn p -> p.resource == "CustomField" && p.action == :destroy end) assert cf_read.scope == :all assert cf_read.granted == true assert cf_create.scope == :all assert cf_create.granted == true assert cf_update.scope == :all assert cf_update.granted == true assert cf_destroy.scope == :all assert cf_destroy.granted == true end test "allows Role full CRUD with scope :all" do permissions = PermissionSets.get_permissions(:admin) role_read = Enum.find(permissions.resources, fn p -> p.resource == "Role" && p.action == :read end) role_create = Enum.find(permissions.resources, fn p -> p.resource == "Role" && p.action == :create end) role_update = Enum.find(permissions.resources, fn p -> p.resource == "Role" && p.action == :update end) role_destroy = Enum.find(permissions.resources, fn p -> p.resource == "Role" && p.action == :destroy end) assert role_read.scope == :all assert role_read.granted == true assert role_create.scope == :all assert role_create.granted == true assert role_update.scope == :all assert role_update.granted == true assert role_destroy.scope == :all assert role_destroy.granted == true end test "has wildcard page permission" do permissions = PermissionSets.get_permissions(:admin) assert "*" in permissions.pages end test "admin pages include explicit /settings and /membership_fee_settings" do permissions = PermissionSets.get_permissions(:admin) assert "/settings" in permissions.pages assert "/membership_fee_settings" in permissions.pages end end describe "get_permissions/1 - MemberGroup resource" do test "own_data has MemberGroup read with scope :linked only" do permissions = PermissionSets.get_permissions(:own_data) mg_read = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :read end) mg_create = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :create end) assert mg_read != nil assert mg_read.scope == :linked assert mg_read.granted == true assert mg_create == nil || mg_create.granted == false end test "read_only has MemberGroup read with scope :all, no create/destroy" do permissions = PermissionSets.get_permissions(:read_only) mg_read = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :read end) mg_create = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :create end) mg_destroy = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :destroy end) assert mg_read != nil assert mg_read.scope == :all assert mg_read.granted == true assert mg_create == nil || mg_create.granted == false assert mg_destroy == nil || mg_destroy.granted == false end test "normal_user has MemberGroup read/create/destroy with scope :all" do permissions = PermissionSets.get_permissions(:normal_user) mg_read = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :read end) mg_create = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :create end) mg_destroy = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :destroy end) assert mg_read != nil assert mg_read.scope == :all assert mg_read.granted == true assert mg_create != nil assert mg_create.scope == :all assert mg_create.granted == true assert mg_destroy != nil assert mg_destroy.scope == :all assert mg_destroy.granted == true end test "admin has MemberGroup read/create/destroy with scope :all" do permissions = PermissionSets.get_permissions(:admin) mg_read = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :read end) mg_create = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :create end) mg_destroy = Enum.find(permissions.resources, fn p -> p.resource == "MemberGroup" && p.action == :destroy end) assert mg_read != nil assert mg_read.scope == :all assert mg_read.granted == true assert mg_create != nil assert mg_create.granted == true assert mg_destroy != nil assert mg_destroy.granted == true end end describe "get_permissions/1 - MembershipFeeType resource" do test "all permission sets have MembershipFeeType read with scope :all" do for set <- PermissionSets.all_permission_sets() do permissions = PermissionSets.get_permissions(set) mft_read = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeType" && p.action == :read end) assert mft_read != nil, "Permission set #{set} should have MembershipFeeType read" assert mft_read.scope == :all assert mft_read.granted == true end end test "only admin has MembershipFeeType create/update/destroy" do for set <- [:own_data, :read_only, :normal_user] do permissions = PermissionSets.get_permissions(set) mft_create = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeType" && p.action == :create end) mft_update = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeType" && p.action == :update end) mft_destroy = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeType" && p.action == :destroy end) assert mft_create == nil || mft_create.granted == false, "Permission set #{set} should not allow MembershipFeeType create" assert mft_update == nil || mft_update.granted == false, "Permission set #{set} should not allow MembershipFeeType update" assert mft_destroy == nil || mft_destroy.granted == false, "Permission set #{set} should not allow MembershipFeeType destroy" end admin_permissions = PermissionSets.get_permissions(:admin) mft_create = Enum.find(admin_permissions.resources, fn p -> p.resource == "MembershipFeeType" && p.action == :create end) mft_update = Enum.find(admin_permissions.resources, fn p -> p.resource == "MembershipFeeType" && p.action == :update end) mft_destroy = Enum.find(admin_permissions.resources, fn p -> p.resource == "MembershipFeeType" && p.action == :destroy end) assert mft_create != nil assert mft_create.scope == :all assert mft_create.granted == true assert mft_update != nil assert mft_update.granted == true assert mft_destroy != nil assert mft_destroy.granted == true end end describe "get_permissions/1 - MembershipFeeCycle resource" do test "all permission sets have MembershipFeeCycle read; own_data uses :linked, others :all" do for set <- PermissionSets.all_permission_sets() do permissions = PermissionSets.get_permissions(set) mfc_read = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeCycle" && p.action == :read end) assert mfc_read != nil, "Permission set #{set} should have MembershipFeeCycle read" assert mfc_read.granted == true expected_scope = if set == :own_data, do: :linked, else: :all assert mfc_read.scope == expected_scope, "Permission set #{set} should have MembershipFeeCycle read scope #{expected_scope}, got #{mfc_read.scope}" end end test "read_only has MembershipFeeCycle read only, no update" do permissions = PermissionSets.get_permissions(:read_only) mfc_update = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeCycle" && p.action == :update end) assert mfc_update == nil || mfc_update.granted == false end test "normal_user has MembershipFeeCycle read/create/update/destroy with scope :all" do permissions = PermissionSets.get_permissions(:normal_user) mfc_read = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeCycle" && p.action == :read end) mfc_create = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeCycle" && p.action == :create end) mfc_update = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeCycle" && p.action == :update end) mfc_destroy = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeCycle" && p.action == :destroy end) assert mfc_read != nil && mfc_read.granted == true assert mfc_create != nil && mfc_create.scope == :all && mfc_create.granted == true assert mfc_update != nil && mfc_update.granted == true assert mfc_destroy != nil && mfc_destroy.scope == :all && mfc_destroy.granted == true end test "admin has MembershipFeeCycle read/create/update/destroy with scope :all" do permissions = PermissionSets.get_permissions(:admin) mfc_read = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeCycle" && p.action == :read end) mfc_create = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeCycle" && p.action == :create end) mfc_update = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeCycle" && p.action == :update end) mfc_destroy = Enum.find(permissions.resources, fn p -> p.resource == "MembershipFeeCycle" && p.action == :destroy end) assert mfc_read != nil assert mfc_read.granted == true assert mfc_create != nil assert mfc_create.granted == true assert mfc_update != nil assert mfc_update.granted == true assert mfc_destroy != nil assert mfc_destroy.granted == true end end describe "valid_permission_set?/1" do test "returns true for valid permission set string" do assert PermissionSets.valid_permission_set?("own_data") == true assert PermissionSets.valid_permission_set?("read_only") == true assert PermissionSets.valid_permission_set?("normal_user") == true assert PermissionSets.valid_permission_set?("admin") == true end test "returns true for valid permission set atom" do assert PermissionSets.valid_permission_set?(:own_data) == true assert PermissionSets.valid_permission_set?(:read_only) == true assert PermissionSets.valid_permission_set?(:normal_user) == true assert PermissionSets.valid_permission_set?(:admin) == true end test "returns false for invalid permission set string" do assert PermissionSets.valid_permission_set?("invalid") == false assert PermissionSets.valid_permission_set?("") == false assert PermissionSets.valid_permission_set?("admin_user") == false end test "returns false for invalid permission set atom" do assert PermissionSets.valid_permission_set?(:invalid) == false assert PermissionSets.valid_permission_set?(:unknown) == false end test "returns false for nil input" do assert PermissionSets.valid_permission_set?(nil) == false end test "returns false for invalid types" do assert PermissionSets.valid_permission_set?(123) == false assert PermissionSets.valid_permission_set?([]) == false assert PermissionSets.valid_permission_set?(%{}) == false assert PermissionSets.valid_permission_set?("") == false end end describe "permission_set_name_to_atom/1" do test "returns {:ok, atom} for valid permission set name" do assert PermissionSets.permission_set_name_to_atom("own_data") == {:ok, :own_data} assert PermissionSets.permission_set_name_to_atom("read_only") == {:ok, :read_only} assert PermissionSets.permission_set_name_to_atom("normal_user") == {:ok, :normal_user} assert PermissionSets.permission_set_name_to_atom("admin") == {:ok, :admin} end test "returns {:error, :invalid_permission_set} for invalid permission set name" do assert PermissionSets.permission_set_name_to_atom("invalid") == {:error, :invalid_permission_set} assert PermissionSets.permission_set_name_to_atom("") == {:error, :invalid_permission_set} assert PermissionSets.permission_set_name_to_atom("admin_user") == {:error, :invalid_permission_set} end test "handles non-existent atom gracefully" do # String.to_existing_atom will raise ArgumentError for non-existent atoms assert PermissionSets.permission_set_name_to_atom("nonexistent_atom_12345") == {:error, :invalid_permission_set} end end describe "get_permissions/1 - error handling" do test "raises ArgumentError for invalid permission set with helpful message" do assert_raise ArgumentError, ~r/invalid permission set: :invalid\. Must be one of:/, fn -> PermissionSets.get_permissions(:invalid) end end test "error message includes all valid permission sets" do error = assert_raise ArgumentError, fn -> PermissionSets.get_permissions(:unknown) end error_message = Exception.message(error) assert error_message =~ "own_data" assert error_message =~ "read_only" assert error_message =~ "normal_user" assert error_message =~ "admin" end end end