defmodule Mv.Authorization.PolicyConsistencyTest do @moduledoc """ Tests to ensure policy consistency across resources. Verifies that resources with scope :own/:linked permissions for READ have corresponding READ bypass policies (as required by the two-tier pattern). """ use ExUnit.Case, async: true alias Mv.Authorization.PermissionSets describe "Policy Pattern Consistency" do test "resources with scope :own/:linked for READ have READ bypass" do # Get all permission sets permission_sets = PermissionSets.all_permission_sets() # Collect all resources with scope :own/:linked for READ resources_with_filter_scope = for permission_set <- permission_sets, permissions = PermissionSets.get_permissions(permission_set), perm <- permissions.resources, perm.action == :read, perm.scope in [:own, :linked], perm.granted == true, do: perm.resource # Remove duplicates unique_resources = Enum.uniq(resources_with_filter_scope) # Expected resources that should have READ bypass expected_resources = ["User", "Member", "CustomFieldValue"] # Verify all expected resources are in the list for resource <- expected_resources do assert resource in unique_resources, "Resource #{resource} has scope :own/:linked for READ but may not have READ bypass policy. " <> "See docs/policy-bypass-vs-haspermission.md for the two-tier pattern." end end test "resources with scope :own/:linked for UPDATE use HasPermission" do # Get all permission sets permission_sets = PermissionSets.all_permission_sets() # Collect all resources with scope :own/:linked for UPDATE resources_with_filter_scope = for permission_set <- permission_sets, permissions = PermissionSets.get_permissions(permission_set), perm <- permissions.resources, perm.action == :update, perm.scope in [:own, :linked], perm.granted == true, do: perm.resource # Remove duplicates unique_resources = Enum.uniq(resources_with_filter_scope) # Expected resources that should use HasPermission for UPDATE expected_resources = ["User", "Member", "CustomFieldValue"] # Verify all expected resources are in the list for resource <- expected_resources do assert resource in unique_resources, "Resource #{resource} should use HasPermission for UPDATE with scope :own/:linked. " <> "See docs/policy-bypass-vs-haspermission.md for the two-tier pattern." end end test "all permission sets grant User.update (own or all)" do # Verify that all permission sets grant User.update # - :own_data, :read_only, :normal_user grant User.update :own # - :admin grants User.update :all (can update all users) permission_sets = PermissionSets.all_permission_sets() for permission_set <- permission_sets do permissions = PermissionSets.get_permissions(permission_set) user_update_perm = Enum.find(permissions.resources, fn perm -> perm.resource == "User" and perm.action == :update end) assert user_update_perm != nil, "Permission set #{permission_set} must grant User.update. " <> "All permission sets must allow users to update credentials." assert user_update_perm.scope in [:own, :all], "Permission set #{permission_set} must grant User.update with scope :own or :all. " <> "Current scope: #{user_update_perm.scope}" assert user_update_perm.granted == true, "Permission set #{permission_set} must grant User.update. " <> "Current granted: #{user_update_perm.granted}" # Non-admin sets should use :own if permission_set != :admin do assert user_update_perm.scope == :own, "Permission set #{permission_set} must grant User.update with scope :own. " <> "Current scope: #{user_update_perm.scope}" end end end end end