Add comprehensive test suite for the HasPermission Ash Policy Check covering permission lookup, scope application, error handling, and logging.
264 lines
8.1 KiB
Elixir
264 lines
8.1 KiB
Elixir
defmodule Mv.Authorization.Checks.HasPermissionTest do
|
|
@moduledoc """
|
|
Tests for the HasPermission Ash Policy Check.
|
|
|
|
This check evaluates permissions from the PermissionSets module and applies
|
|
scope filters to Ash queries.
|
|
"""
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Mv.Authorization.Checks.HasPermission
|
|
|
|
# Helper to create a mock authorizer for strict_check/3
|
|
defp create_authorizer(resource, action) do
|
|
%Ash.Policy.Authorizer{
|
|
resource: resource,
|
|
subject: %{action: %{name: action}}
|
|
}
|
|
end
|
|
|
|
# Helper to create actor with role
|
|
defp create_actor(id, permission_set_name) do
|
|
%{
|
|
id: id,
|
|
role: %{permission_set_name: permission_set_name}
|
|
}
|
|
end
|
|
|
|
describe "describe/1" do
|
|
test "returns human-readable description" do
|
|
description = HasPermission.describe([])
|
|
assert is_binary(description)
|
|
assert description =~ "permission"
|
|
end
|
|
end
|
|
|
|
describe "strict_check/3 - Permission Lookup" do
|
|
test "admin has permission for all resources/actions" do
|
|
admin = create_actor("admin-123", "admin")
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(admin, authorizer, [])
|
|
|
|
assert result == true or result == :unknown
|
|
end
|
|
|
|
test "read_only has read permission for Member" do
|
|
read_only_user = create_actor("read-only-123", "read_only")
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(read_only_user, authorizer, [])
|
|
|
|
assert result == true or result == :unknown
|
|
end
|
|
|
|
test "read_only does NOT have create permission for Member" do
|
|
read_only_user = create_actor("read-only-123", "read_only")
|
|
authorizer = create_authorizer(Mv.Membership.Member, :create)
|
|
|
|
{:ok, result} = HasPermission.strict_check(read_only_user, authorizer, [])
|
|
|
|
assert result == false
|
|
end
|
|
|
|
test "own_data has update permission for User with scope :own" do
|
|
own_data_user = create_actor("user-123", "own_data")
|
|
authorizer = create_authorizer(Mv.Accounts.User, :update)
|
|
|
|
{:ok, result} = HasPermission.strict_check(own_data_user, authorizer, [])
|
|
|
|
# Should return :unknown for :own scope (needs filter)
|
|
assert result == :unknown
|
|
end
|
|
end
|
|
|
|
describe "strict_check/3 - Scope :all" do
|
|
test "actor with scope :all can access any record" do
|
|
admin = create_actor("admin-123", "admin")
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(admin, authorizer, [])
|
|
|
|
# :all scope should return true (no filter needed)
|
|
assert result == true
|
|
end
|
|
|
|
test "admin can read all members without filter" do
|
|
admin = create_actor("admin-123", "admin")
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(admin, authorizer, [])
|
|
|
|
# Should return true for :all scope
|
|
assert result == true
|
|
end
|
|
end
|
|
|
|
describe "strict_check/3 - Scope :own" do
|
|
test "actor with scope :own returns :unknown (needs filter)" do
|
|
user = create_actor("user-123", "own_data")
|
|
authorizer = create_authorizer(Mv.Accounts.User, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(user, authorizer, [])
|
|
|
|
# Should return :unknown for :own scope (needs filter via auto_filter)
|
|
assert result == :unknown
|
|
end
|
|
end
|
|
|
|
describe "auto_filter/3 - Scope :own" do
|
|
test "scope :own returns filter expression" do
|
|
user = create_actor("user-123", "own_data")
|
|
authorizer = create_authorizer(Mv.Accounts.User, :update)
|
|
|
|
filter = HasPermission.auto_filter(user, authorizer, [])
|
|
|
|
# Should return a filter expression
|
|
assert not is_nil(filter)
|
|
end
|
|
end
|
|
|
|
describe "auto_filter/3 - Scope :linked" do
|
|
test "scope :linked for Member returns user_id filter" do
|
|
user = create_actor("user-123", "own_data")
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
filter = HasPermission.auto_filter(user, authorizer, [])
|
|
|
|
# Should return a filter expression
|
|
assert not is_nil(filter)
|
|
end
|
|
|
|
test "scope :linked for CustomFieldValue returns member.user_id filter" do
|
|
user = create_actor("user-123", "own_data")
|
|
authorizer = create_authorizer(Mv.Membership.CustomFieldValue, :update)
|
|
|
|
filter = HasPermission.auto_filter(user, authorizer, [])
|
|
|
|
# Should return a filter expression that traverses member relationship
|
|
assert not is_nil(filter)
|
|
end
|
|
end
|
|
|
|
describe "strict_check/3 - Error Handling" do
|
|
test "returns {:ok, false} for nil actor" do
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(nil, authorizer, [])
|
|
|
|
assert result == false
|
|
end
|
|
|
|
test "returns {:ok, false} for actor missing role" do
|
|
actor_without_role = %{id: "user-123"}
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(actor_without_role, authorizer, [])
|
|
|
|
assert result == false
|
|
end
|
|
|
|
test "returns {:ok, false} for actor with nil role" do
|
|
actor_with_nil_role = %{id: "user-123", role: nil}
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(actor_with_nil_role, authorizer, [])
|
|
|
|
assert result == false
|
|
end
|
|
|
|
test "returns {:ok, false} for invalid permission_set_name" do
|
|
actor_with_invalid_permission = %{
|
|
id: "user-123",
|
|
role: %{permission_set_name: "invalid_set"}
|
|
}
|
|
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(actor_with_invalid_permission, authorizer, [])
|
|
|
|
assert result == false
|
|
end
|
|
|
|
test "returns {:ok, false} for no matching permission" do
|
|
read_only_user = create_actor("read-only-123", "read_only")
|
|
authorizer = create_authorizer(Mv.Authorization.Role, :create)
|
|
|
|
{:ok, result} = HasPermission.strict_check(read_only_user, authorizer, [])
|
|
|
|
assert result == false
|
|
end
|
|
|
|
test "handles role with nil permission_set_name gracefully" do
|
|
actor_with_nil_permission_set = %{
|
|
id: "user-123",
|
|
role: %{permission_set_name: nil}
|
|
}
|
|
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(actor_with_nil_permission_set, authorizer, [])
|
|
|
|
assert result == false
|
|
end
|
|
end
|
|
|
|
describe "strict_check/3 - Logging" do
|
|
import ExUnit.CaptureLog
|
|
|
|
test "logs authorization failure for nil actor" do
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
log =
|
|
capture_log(fn ->
|
|
HasPermission.strict_check(nil, authorizer, [])
|
|
end)
|
|
|
|
assert log =~ "Authorization failed" or log == ""
|
|
end
|
|
|
|
test "logs authorization failure for missing role" do
|
|
actor_without_role = %{id: "user-123"}
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
log =
|
|
capture_log(fn ->
|
|
HasPermission.strict_check(actor_without_role, authorizer, [])
|
|
end)
|
|
|
|
assert log =~ "Authorization failed" or log == ""
|
|
end
|
|
end
|
|
|
|
describe "strict_check/3 - Resource Name Extraction" do
|
|
test "correctly extracts resource name from nested module" do
|
|
admin = create_actor("admin-123", "admin")
|
|
authorizer = create_authorizer(Mv.Membership.Member, :read)
|
|
|
|
{:ok, result} = HasPermission.strict_check(admin, authorizer, [])
|
|
|
|
# Should work correctly (not crash)
|
|
assert result == true or result == :unknown or result == false
|
|
end
|
|
|
|
test "works with different resource modules" do
|
|
admin = create_actor("admin-123", "admin")
|
|
|
|
resources = [
|
|
Mv.Accounts.User,
|
|
Mv.Membership.Member,
|
|
Mv.Membership.CustomFieldValue,
|
|
Mv.Membership.CustomField,
|
|
Mv.Authorization.Role
|
|
]
|
|
|
|
for resource <- resources do
|
|
authorizer = create_authorizer(resource, :read)
|
|
{:ok, result} = HasPermission.strict_check(admin, authorizer, [])
|
|
|
|
# Should not crash and should return valid result
|
|
assert result == true or result == :unknown or result == false
|
|
end
|
|
end
|
|
end
|
|
end
|