Add actor parameter to all tests requiring authorization

This commit adds actor: system_actor to all Ash operations in tests that
require authorization.
This commit is contained in:
Moritz 2026-01-23 20:00:24 +01:00
parent 686f69c9e9
commit 0f48a9b15a
Signed by: moritz
GPG key ID: 1020A035E5DD0824
75 changed files with 4686 additions and 2859 deletions

View file

@ -16,8 +16,13 @@ defmodule Mv.Membership.MemberPoliciesTest do
require Ash.Query
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
%{actor: system_actor}
end
# Helper to create a role with a specific permission set
defp create_role_with_permission_set(permission_set_name) do
defp create_role_with_permission_set(permission_set_name, _actor) do
role_name = "Test Role #{permission_set_name} #{System.unique_integer([:positive])}"
case Authorization.create_role(%{
@ -32,9 +37,9 @@ defmodule Mv.Membership.MemberPoliciesTest do
# Helper to create a user with a specific permission set
# Returns user with role preloaded (required for authorization)
defp create_user_with_permission_set(permission_set_name) do
defp create_user_with_permission_set(permission_set_name, actor) do
# Create role with permission set
role = create_role_with_permission_set(permission_set_name)
role = create_role_with_permission_set(permission_set_name, actor)
# Create user
{:ok, user} =
@ -43,28 +48,28 @@ defmodule Mv.Membership.MemberPoliciesTest do
email: "user#{System.unique_integer([:positive])}@example.com",
password: "testpassword123"
})
|> Ash.create()
|> Ash.create(actor: actor)
# Assign role to user
{:ok, user} =
user
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:role, role, type: :append_and_remove)
|> Ash.update()
|> Ash.update(actor: actor)
# Reload user with role preloaded (critical for authorization!)
{:ok, user_with_role} = Ash.load(user, :role, domain: Mv.Accounts)
{:ok, user_with_role} = Ash.load(user, :role, domain: Mv.Accounts, actor: actor)
user_with_role
end
# Helper to create an admin user (for creating test fixtures)
defp create_admin_user do
create_user_with_permission_set("admin")
defp create_admin_user(actor) do
create_user_with_permission_set("admin", actor)
end
# Helper to create a member linked to a user
defp create_linked_member_for_user(user) do
admin = create_admin_user()
defp create_linked_member_for_user(user, actor) do
admin = create_admin_user(actor)
# Create member
# NOTE: We need to ensure the member is actually persisted to the database
@ -96,8 +101,8 @@ defmodule Mv.Membership.MemberPoliciesTest do
end
# Helper to create an unlinked member (no user relationship)
defp create_unlinked_member do
admin = create_admin_user()
defp create_unlinked_member(actor) do
admin = create_admin_user(actor)
{:ok, member} =
Membership.Member
@ -112,14 +117,16 @@ defmodule Mv.Membership.MemberPoliciesTest do
end
describe "own_data permission set (Mitglied)" do
setup do
user = create_user_with_permission_set("own_data")
linked_member = create_linked_member_for_user(user)
unlinked_member = create_unlinked_member()
setup %{actor: actor} do
user = create_user_with_permission_set("own_data", actor)
linked_member = create_linked_member_for_user(user, actor)
unlinked_member = create_unlinked_member(actor)
# Reload user to get updated member_id
{:ok, user} = Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role])
{:ok, user} = Ash.load(user, :member, domain: Mv.Accounts)
{:ok, user} =
Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role], actor: actor)
{:ok, user} = Ash.load(user, :member, domain: Mv.Accounts, actor: actor)
%{user: user, linked_member: linked_member, unlinked_member: unlinked_member}
end
@ -165,7 +172,10 @@ defmodule Mv.Membership.MemberPoliciesTest do
end
end
test "list members returns only linked member", %{user: user, linked_member: linked_member} do
test "list members returns only linked member", %{
user: user,
linked_member: linked_member
} do
{:ok, members} = Ash.read(Membership.Member, actor: user, domain: Mv.Membership)
# Should only return the linked member (scope :linked filters)
@ -185,7 +195,10 @@ defmodule Mv.Membership.MemberPoliciesTest do
end
end
test "cannot destroy member (returns forbidden)", %{user: user, linked_member: linked_member} do
test "cannot destroy member (returns forbidden)", %{
user: user,
linked_member: linked_member
} do
assert_raise Ash.Error.Forbidden, fn ->
Ash.destroy!(linked_member, actor: user)
end
@ -193,13 +206,14 @@ defmodule Mv.Membership.MemberPoliciesTest do
end
describe "read_only permission set (Vorstand/Buchhaltung)" do
setup do
user = create_user_with_permission_set("read_only")
linked_member = create_linked_member_for_user(user)
unlinked_member = create_unlinked_member()
setup %{actor: actor} do
user = create_user_with_permission_set("read_only", actor)
linked_member = create_linked_member_for_user(user, actor)
unlinked_member = create_unlinked_member(actor)
# Reload user to get updated member_id
{:ok, user} = Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role])
{:ok, user} =
Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role], actor: actor)
%{user: user, linked_member: linked_member, unlinked_member: unlinked_member}
end
@ -217,7 +231,10 @@ defmodule Mv.Membership.MemberPoliciesTest do
assert unlinked_member.id in member_ids
end
test "can read individual member", %{user: user, unlinked_member: unlinked_member} do
test "can read individual member", %{
user: user,
unlinked_member: unlinked_member
} do
{:ok, member} =
Ash.get(Membership.Member, unlinked_member.id, actor: user, domain: Mv.Membership)
@ -258,13 +275,14 @@ defmodule Mv.Membership.MemberPoliciesTest do
end
describe "normal_user permission set (Kassenwart)" do
setup do
user = create_user_with_permission_set("normal_user")
linked_member = create_linked_member_for_user(user)
unlinked_member = create_unlinked_member()
setup %{actor: actor} do
user = create_user_with_permission_set("normal_user", actor)
linked_member = create_linked_member_for_user(user, actor)
unlinked_member = create_unlinked_member(actor)
# Reload user to get updated member_id
{:ok, user} = Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role])
{:ok, user} =
Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role], actor: actor)
%{user: user, linked_member: linked_member, unlinked_member: unlinked_member}
end
@ -315,13 +333,14 @@ defmodule Mv.Membership.MemberPoliciesTest do
end
describe "admin permission set" do
setup do
user = create_user_with_permission_set("admin")
linked_member = create_linked_member_for_user(user)
unlinked_member = create_unlinked_member()
setup %{actor: actor} do
user = create_user_with_permission_set("admin", actor)
linked_member = create_linked_member_for_user(user, actor)
unlinked_member = create_unlinked_member(actor)
# Reload user to get updated member_id
{:ok, user} = Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role])
{:ok, user} =
Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role], actor: actor)
%{user: user, linked_member: linked_member, unlinked_member: unlinked_member}
end
@ -361,7 +380,10 @@ defmodule Mv.Membership.MemberPoliciesTest do
assert updated_member.first_name == "Updated"
end
test "can destroy any member", %{user: user, unlinked_member: unlinked_member} do
test "can destroy any member", %{
user: user,
unlinked_member: unlinked_member
} do
:ok = Ash.destroy(unlinked_member, actor: user)
# Verify member is deleted
@ -370,19 +392,24 @@ defmodule Mv.Membership.MemberPoliciesTest do
end
describe "special case: user can always READ linked member" do
# Note: The special case policy only applies to :read actions.
# Updates are handled by HasPermission with :linked scope (if permission exists).
setup %{actor: _actor} do
# Note: The special case policy only applies to :read actions.
# Updates are handled by HasPermission with :linked scope (if permission exists).
:ok
end
test "read_only user can read linked member (via special case bypass)" do
test "read_only user can read linked member (via special case bypass)", %{actor: actor} do
# read_only has Member.read scope :all, but the special case ensures
# users can ALWAYS read their linked member, even if they had no read permission.
# This test verifies the special case works independently of permission sets.
user = create_user_with_permission_set("read_only")
linked_member = create_linked_member_for_user(user)
user = create_user_with_permission_set("read_only", actor)
linked_member = create_linked_member_for_user(user, actor)
# Reload user to get updated member_id
{:ok, user} = Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role])
{:ok, user} = Ash.load(user, :member, domain: Mv.Accounts)
{:ok, user} =
Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role], actor: actor)
{:ok, user} = Ash.load(user, :member, domain: Mv.Accounts, actor: actor)
# Should succeed (special case bypass policy for :read takes precedence)
{:ok, member} =
@ -391,15 +418,17 @@ defmodule Mv.Membership.MemberPoliciesTest do
assert member.id == linked_member.id
end
test "own_data user can read linked member (via special case bypass)" do
test "own_data user can read linked member (via special case bypass)", %{actor: actor} do
# own_data has Member.read scope :linked, but the special case ensures
# users can ALWAYS read their linked member regardless of permission set.
user = create_user_with_permission_set("own_data")
linked_member = create_linked_member_for_user(user)
user = create_user_with_permission_set("own_data", actor)
linked_member = create_linked_member_for_user(user, actor)
# Reload user to get updated member_id
{:ok, user} = Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role])
{:ok, user} = Ash.load(user, :member, domain: Mv.Accounts)
{:ok, user} =
Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role], actor: actor)
{:ok, user} = Ash.load(user, :member, domain: Mv.Accounts, actor: actor)
# Should succeed (special case bypass policy for :read takes precedence)
{:ok, member} =
@ -408,15 +437,19 @@ defmodule Mv.Membership.MemberPoliciesTest do
assert member.id == linked_member.id
end
test "own_data user can update linked member (via HasPermission :linked scope)" do
test "own_data user can update linked member (via HasPermission :linked scope)", %{
actor: actor
} do
# Update is NOT handled by special case - it's handled by HasPermission
# with :linked scope. own_data has Member.update scope :linked.
user = create_user_with_permission_set("own_data")
linked_member = create_linked_member_for_user(user)
user = create_user_with_permission_set("own_data", actor)
linked_member = create_linked_member_for_user(user, actor)
# Reload user to get updated member_id
{:ok, user} = Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role])
{:ok, user} = Ash.load(user, :member, domain: Mv.Accounts)
{:ok, user} =
Ash.get(Accounts.User, user.id, domain: Mv.Accounts, load: [:role], actor: actor)
{:ok, user} = Ash.load(user, :member, domain: Mv.Accounts, actor: actor)
# Should succeed via HasPermission check (not special case)
{:ok, updated_member} =