mitgliederverwaltung/test/mv_web/authorization_test.exs
Moritz ff9c8d2d64
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
2026-01-08 16:16:53 +01:00

219 lines
7.6 KiB
Elixir

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