feat: add UI-level authorization helpers
Implement MvWeb.Authorization module with can?/3 and can_access_page?/2 functions for conditional rendering in LiveView templates. - can?/3 supports both resource atoms and record structs with scope checking - can_access_page?/2 checks page access permissions - All functions use PermissionSets module for consistency with backend - Graceful handling of nil users and invalid permission sets - Comprehensive test coverage with 17 test cases
This commit is contained in:
parent
5253286722
commit
c6b5b7a22e
3 changed files with 424 additions and 0 deletions
219
test/mv_web/authorization_test.exs
Normal file
219
test/mv_web/authorization_test.exs
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
defmodule MvWeb.AuthorizationTest do
|
||||
@moduledoc """
|
||||
Tests for UI-level authorization helpers.
|
||||
"""
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias MvWeb.Authorization
|
||||
alias Mv.Membership.Member
|
||||
alias Mv.Accounts.User
|
||||
|
||||
describe "can?/3 with resource atom" do
|
||||
test "returns true when user has permission for resource+action" do
|
||||
admin = %{
|
||||
id: "admin-123",
|
||||
role: %{permission_set_name: "admin"}
|
||||
}
|
||||
|
||||
assert Authorization.can?(admin, :create, Mv.Membership.Member) == true
|
||||
assert Authorization.can?(admin, :read, Mv.Membership.Member) == true
|
||||
assert Authorization.can?(admin, :update, Mv.Membership.Member) == true
|
||||
assert Authorization.can?(admin, :destroy, Mv.Membership.Member) == true
|
||||
end
|
||||
|
||||
test "returns false when user lacks permission" do
|
||||
read_only_user = %{
|
||||
id: "read-only-123",
|
||||
role: %{permission_set_name: "read_only"}
|
||||
}
|
||||
|
||||
assert Authorization.can?(read_only_user, :create, Mv.Membership.Member) == false
|
||||
assert Authorization.can?(read_only_user, :read, Mv.Membership.Member) == true
|
||||
assert Authorization.can?(read_only_user, :update, Mv.Membership.Member) == false
|
||||
assert Authorization.can?(read_only_user, :destroy, Mv.Membership.Member) == false
|
||||
end
|
||||
|
||||
test "returns false for nil user" do
|
||||
assert Authorization.can?(nil, :create, Mv.Membership.Member) == false
|
||||
assert Authorization.can?(nil, :read, Mv.Membership.Member) == false
|
||||
end
|
||||
|
||||
test "admin can manage roles" do
|
||||
admin = %{
|
||||
id: "admin-123",
|
||||
role: %{permission_set_name: "admin"}
|
||||
}
|
||||
|
||||
assert Authorization.can?(admin, :create, Mv.Authorization.Role) == true
|
||||
assert Authorization.can?(admin, :read, Mv.Authorization.Role) == true
|
||||
assert Authorization.can?(admin, :update, Mv.Authorization.Role) == true
|
||||
assert Authorization.can?(admin, :destroy, Mv.Authorization.Role) == true
|
||||
end
|
||||
|
||||
test "non-admin cannot manage roles" do
|
||||
normal_user = %{
|
||||
id: "normal-123",
|
||||
role: %{permission_set_name: "normal_user"}
|
||||
}
|
||||
|
||||
assert Authorization.can?(normal_user, :create, Mv.Authorization.Role) == false
|
||||
assert Authorization.can?(normal_user, :read, Mv.Authorization.Role) == false
|
||||
assert Authorization.can?(normal_user, :update, Mv.Authorization.Role) == false
|
||||
assert Authorization.can?(normal_user, :destroy, Mv.Authorization.Role) == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "can?/3 with record struct - scope :all" do
|
||||
test "admin can update any member" do
|
||||
admin = %{
|
||||
id: "admin-123",
|
||||
role: %{permission_set_name: "admin"}
|
||||
}
|
||||
|
||||
member1 = %Member{id: "member-1", user: %User{id: "other-user"}}
|
||||
member2 = %Member{id: "member-2", user: %User{id: "another-user"}}
|
||||
|
||||
assert Authorization.can?(admin, :update, member1) == true
|
||||
assert Authorization.can?(admin, :update, member2) == true
|
||||
end
|
||||
|
||||
test "normal_user can update any member" do
|
||||
normal_user = %{
|
||||
id: "normal-123",
|
||||
role: %{permission_set_name: "normal_user"}
|
||||
}
|
||||
|
||||
member = %Member{id: "member-1", user: %User{id: "other-user"}}
|
||||
|
||||
assert Authorization.can?(normal_user, :update, member) == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "can?/3 with record struct - scope :own" do
|
||||
test "user can update own User record" do
|
||||
user = %{
|
||||
id: "user-123",
|
||||
role: %{permission_set_name: "own_data"}
|
||||
}
|
||||
|
||||
own_user_record = %User{id: "user-123"}
|
||||
other_user_record = %User{id: "other-user"}
|
||||
|
||||
assert Authorization.can?(user, :update, own_user_record) == true
|
||||
assert Authorization.can?(user, :update, other_user_record) == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "can?/3 with record struct - scope :linked" do
|
||||
test "user can update linked member" do
|
||||
user = %{
|
||||
id: "user-123",
|
||||
role: %{permission_set_name: "own_data"}
|
||||
}
|
||||
|
||||
# Member has_one :user (inverse relationship)
|
||||
linked_member = %Member{id: "member-1", user: %User{id: "user-123"}}
|
||||
unlinked_member = %Member{id: "member-2", user: nil}
|
||||
unlinked_member_other = %Member{id: "member-3", user: %User{id: "other-user"}}
|
||||
|
||||
assert Authorization.can?(user, :update, linked_member) == true
|
||||
assert Authorization.can?(user, :update, unlinked_member) == false
|
||||
assert Authorization.can?(user, :update, unlinked_member_other) == false
|
||||
end
|
||||
|
||||
test "user can update CustomFieldValue of linked member" do
|
||||
user = %{
|
||||
id: "user-123",
|
||||
role: %{permission_set_name: "own_data"}
|
||||
}
|
||||
|
||||
linked_cfv = %Mv.Membership.CustomFieldValue{
|
||||
id: "cfv-1",
|
||||
member: %Member{id: "member-1", user: %User{id: "user-123"}}
|
||||
}
|
||||
|
||||
unlinked_cfv = %Mv.Membership.CustomFieldValue{
|
||||
id: "cfv-2",
|
||||
member: %Member{id: "member-2", user: nil}
|
||||
}
|
||||
|
||||
unlinked_cfv_other = %Mv.Membership.CustomFieldValue{
|
||||
id: "cfv-3",
|
||||
member: %Member{id: "member-3", user: %User{id: "other-user"}}
|
||||
}
|
||||
|
||||
assert Authorization.can?(user, :update, linked_cfv) == true
|
||||
assert Authorization.can?(user, :update, unlinked_cfv) == false
|
||||
assert Authorization.can?(user, :update, unlinked_cfv_other) == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "can_access_page?/2" do
|
||||
test "admin can access all pages via wildcard" do
|
||||
admin = %{
|
||||
id: "admin-123",
|
||||
role: %{permission_set_name: "admin"}
|
||||
}
|
||||
|
||||
assert Authorization.can_access_page?(admin, "/admin/roles") == true
|
||||
assert Authorization.can_access_page?(admin, "/members") == true
|
||||
assert Authorization.can_access_page?(admin, "/any/page") == true
|
||||
end
|
||||
|
||||
test "read_only user can access allowed pages" do
|
||||
read_only_user = %{
|
||||
id: "read-only-123",
|
||||
role: %{permission_set_name: "read_only"}
|
||||
}
|
||||
|
||||
assert Authorization.can_access_page?(read_only_user, "/") == true
|
||||
assert Authorization.can_access_page?(read_only_user, "/members") == true
|
||||
assert Authorization.can_access_page?(read_only_user, "/members/123") == true
|
||||
assert Authorization.can_access_page?(read_only_user, "/admin/roles") == false
|
||||
end
|
||||
|
||||
test "matches dynamic routes correctly" do
|
||||
read_only_user = %{
|
||||
id: "read-only-123",
|
||||
role: %{permission_set_name: "read_only"}
|
||||
}
|
||||
|
||||
assert Authorization.can_access_page?(read_only_user, "/members/123") == true
|
||||
assert Authorization.can_access_page?(read_only_user, "/members/abc") == true
|
||||
assert Authorization.can_access_page?(read_only_user, "/members/123/edit") == false
|
||||
end
|
||||
|
||||
test "returns false for nil user" do
|
||||
assert Authorization.can_access_page?(nil, "/members") == false
|
||||
assert Authorization.can_access_page?(nil, "/admin/roles") == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "error handling" do
|
||||
test "user without role returns false" do
|
||||
user_without_role = %{id: "user-123", role: nil}
|
||||
|
||||
assert Authorization.can?(user_without_role, :create, Mv.Membership.Member) == false
|
||||
assert Authorization.can_access_page?(user_without_role, "/members") == false
|
||||
end
|
||||
|
||||
test "user with invalid permission_set_name returns false" do
|
||||
user_with_invalid_permission = %{
|
||||
id: "user-123",
|
||||
role: %{permission_set_name: "invalid_set"}
|
||||
}
|
||||
|
||||
assert Authorization.can?(user_with_invalid_permission, :create, Mv.Membership.Member) ==
|
||||
false
|
||||
|
||||
assert Authorization.can_access_page?(user_with_invalid_permission, "/members") == false
|
||||
end
|
||||
|
||||
test "handles missing fields gracefully" do
|
||||
user_missing_role = %{id: "user-123"}
|
||||
|
||||
assert Authorization.can?(user_missing_role, :create, Mv.Membership.Member) == false
|
||||
assert Authorization.can_access_page?(user_missing_role, "/members") == false
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue