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:
parent
686f69c9e9
commit
0f48a9b15a
75 changed files with 4686 additions and 2859 deletions
|
|
@ -8,8 +8,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
use MvWeb.ConnCase, async: true
|
||||
require Ash.Query
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
describe "E2E: New OIDC user registration" do
|
||||
test "new user can register via OIDC", %{conn: _conn} do
|
||||
test "new user can register via OIDC", %{conn: _conn, actor: actor} do
|
||||
# Simulate OIDC callback for brand new user
|
||||
user_info = %{
|
||||
"sub" => "new_oidc_user_123",
|
||||
|
|
@ -18,10 +23,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Call register action
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert {:ok, new_user} = result
|
||||
assert to_string(new_user.email) == "newuser@example.com"
|
||||
|
|
@ -30,17 +38,20 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Verify user can be found by oidc_id
|
||||
{:ok, [found_user]} =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert found_user.id == new_user.id
|
||||
end
|
||||
end
|
||||
|
||||
describe "E2E: Existing OIDC user sign-in" do
|
||||
test "existing OIDC user can sign in and email updates", %{conn: _conn} do
|
||||
test "existing OIDC user can sign in and email updates", %{conn: _conn, actor: actor} do
|
||||
# Create OIDC user
|
||||
user =
|
||||
create_test_user(%{
|
||||
|
|
@ -56,10 +67,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Register (upsert) with new email
|
||||
{:ok, updated_user} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: updated_user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: updated_user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Same user, updated email
|
||||
assert updated_user.id == user.id
|
||||
|
|
@ -70,7 +84,7 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
describe "E2E: OIDC with existing password account (Email Collision)" do
|
||||
test "OIDC registration with password account email triggers PasswordVerificationRequired",
|
||||
%{conn: _conn} do
|
||||
%{conn: _conn, actor: actor} do
|
||||
# Step 1: Create a password-only user
|
||||
password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -86,10 +100,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Step 3: Should fail with PasswordVerificationRequired
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
|
@ -106,7 +123,7 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
end
|
||||
|
||||
test "full E2E flow: OIDC collision -> password verification -> account linked",
|
||||
%{conn: _conn} do
|
||||
%{conn: _conn, actor: actor} do
|
||||
# Step 1: Create password user
|
||||
password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -122,10 +139,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
}
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Extract the error
|
||||
password_error =
|
||||
|
|
@ -142,12 +162,12 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
{:ok, linked_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Query.filter(id == ^password_user.id)
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: actor)
|
||||
|> Ash.Changeset.for_update(:link_oidc_id, %{
|
||||
oidc_id: user_info["sub"],
|
||||
oidc_user_info: user_info
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# Verify account is now linked
|
||||
assert linked_user.id == password_user.id
|
||||
|
|
@ -158,17 +178,20 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Step 5: User can now sign in via OIDC
|
||||
{:ok, [signed_in_user]} =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert signed_in_user.id == password_user.id
|
||||
assert signed_in_user.oidc_id == "oidc_link_888"
|
||||
end
|
||||
|
||||
test "E2E: OIDC collision with different email at provider updates email after linking",
|
||||
%{conn: _conn} do
|
||||
%{conn: _conn, actor: actor} do
|
||||
# Password user with old email
|
||||
password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -199,12 +222,12 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
{:ok, linked_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Query.filter(id == ^password_user.id)
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: actor)
|
||||
|> Ash.Changeset.for_update(:link_oidc_id, %{
|
||||
oidc_id: updated_user_info["sub"],
|
||||
oidc_user_info: updated_user_info
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# Email should be updated to match OIDC provider
|
||||
assert to_string(linked_user.email) == "new@example.com"
|
||||
|
|
@ -213,7 +236,10 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
end
|
||||
|
||||
describe "E2E: OIDC with linked member" do
|
||||
test "E2E: email sync to member when linking OIDC to password account", %{conn: _conn} do
|
||||
test "E2E: email sync to member when linking OIDC to password account", %{
|
||||
conn: _conn,
|
||||
actor: actor
|
||||
} do
|
||||
# Create member
|
||||
member =
|
||||
Ash.Seed.seed!(Mv.Membership.Member, %{
|
||||
|
|
@ -239,10 +265,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Collision detected
|
||||
{:error, %Ash.Error.Invalid{}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# After password verification, link OIDC with NEW email
|
||||
updated_user_info = %{
|
||||
|
|
@ -253,24 +282,27 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
{:ok, linked_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Query.filter(id == ^password_user.id)
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: actor)
|
||||
|> Ash.Changeset.for_update(:link_oidc_id, %{
|
||||
oidc_id: updated_user_info["sub"],
|
||||
oidc_user_info: updated_user_info
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# User email updated
|
||||
assert to_string(linked_user.email) == "newmember@example.com"
|
||||
|
||||
# Member email should be synced
|
||||
{:ok, updated_member} = Ash.get(Mv.Membership.Member, member.id)
|
||||
{:ok, updated_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
|
||||
assert to_string(updated_member.email) == "newmember@example.com"
|
||||
end
|
||||
end
|
||||
|
||||
describe "E2E: Security scenarios" do
|
||||
test "E2E: password-only user cannot be accessed via OIDC without password", %{conn: _conn} do
|
||||
test "E2E: password-only user cannot be accessed via OIDC without password", %{
|
||||
conn: _conn,
|
||||
actor: actor
|
||||
} do
|
||||
# Create password user
|
||||
_password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -287,10 +319,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Sign-in should fail (no matching oidc_id)
|
||||
result =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
case result do
|
||||
{:ok, []} ->
|
||||
|
|
@ -305,17 +340,23 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Registration should trigger password requirement
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert Enum.any?(errors, fn err ->
|
||||
match?(%Mv.Accounts.User.Errors.PasswordVerificationRequired{}, err)
|
||||
end)
|
||||
end
|
||||
|
||||
test "E2E: user with oidc_id cannot be hijacked by different OIDC provider", %{conn: _conn} do
|
||||
test "E2E: user with oidc_id cannot be hijacked by different OIDC provider", %{
|
||||
conn: _conn,
|
||||
actor: actor
|
||||
} do
|
||||
# User linked to OIDC provider A
|
||||
_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -331,10 +372,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Should trigger hard error (not PasswordVerificationRequired)
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should have hard error about "already linked to a different OIDC account"
|
||||
assert Enum.any?(errors, fn
|
||||
|
|
@ -351,7 +395,10 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "E2E: empty string oidc_id is treated as password-only account", %{conn: _conn} do
|
||||
test "E2E: empty string oidc_id is treated as password-only account", %{
|
||||
conn: _conn,
|
||||
actor: actor
|
||||
} do
|
||||
# User with empty oidc_id
|
||||
_password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -367,10 +414,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
}
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should require password (empty string = no OIDC)
|
||||
assert Enum.any?(errors, fn err ->
|
||||
|
|
@ -380,32 +430,38 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
end
|
||||
|
||||
describe "E2E: Error scenarios" do
|
||||
test "E2E: OIDC registration without oidc_id fails", %{conn: _conn} do
|
||||
test "E2E: OIDC registration without oidc_id fails", %{conn: _conn, actor: actor} do
|
||||
user_info = %{
|
||||
"preferred_username" => "noid@example.com"
|
||||
}
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert Enum.any?(errors, fn err ->
|
||||
match?(%Ash.Error.Changes.InvalidChanges{}, err)
|
||||
end)
|
||||
end
|
||||
|
||||
test "E2E: OIDC registration without email fails", %{conn: _conn} do
|
||||
test "E2E: OIDC registration without email fails", %{conn: _conn, actor: actor} do
|
||||
user_info = %{
|
||||
"sub" => "noemail_123"
|
||||
}
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert Enum.any?(errors, fn err ->
|
||||
match?(%Ash.Error.Changes.Required{field: :email}, err)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,13 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
"""
|
||||
use MvWeb.ConnCase, async: true
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
describe "OIDC user updates email to available email" do
|
||||
test "should succeed and update email" do
|
||||
test "should succeed and update email", %{actor: actor} do
|
||||
# Create OIDC user
|
||||
{:ok, oidc_user} =
|
||||
Mv.Accounts.User
|
||||
|
|
@ -14,7 +19,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
email: "original@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_123")
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# User logs in via OIDC with NEW email
|
||||
user_info = %{
|
||||
|
|
@ -23,10 +28,13 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should succeed and email should be updated
|
||||
assert {:ok, updated_user} = result
|
||||
|
|
@ -37,7 +45,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
end
|
||||
|
||||
describe "OIDC user updates email to email of passwordless user" do
|
||||
test "should fail with clear error message" do
|
||||
test "should fail with clear error message", %{actor: actor} do
|
||||
# Create OIDC user
|
||||
{:ok, _oidc_user} =
|
||||
Mv.Accounts.User
|
||||
|
|
@ -45,7 +53,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
email: "oidcuser@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_456")
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# Create passwordless user with target email
|
||||
{:ok, _passwordless_user} =
|
||||
|
|
@ -53,7 +61,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
|> Ash.Changeset.for_create(:create_user, %{
|
||||
email: "taken@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# OIDC user tries to update email to taken email
|
||||
user_info = %{
|
||||
|
|
@ -62,10 +70,13 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should fail with email update conflict error
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
|
@ -88,7 +99,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
end
|
||||
|
||||
describe "OIDC user updates email to email of password-protected user" do
|
||||
test "should fail with clear error message" do
|
||||
test "should fail with clear error message", %{actor: actor} do
|
||||
# Create OIDC user
|
||||
{:ok, _oidc_user} =
|
||||
Mv.Accounts.User
|
||||
|
|
@ -96,7 +107,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
email: "oidcuser2@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_789")
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# Create password user with target email (explicitly NO oidc_id)
|
||||
password_user =
|
||||
|
|
@ -106,14 +117,14 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
})
|
||||
|
||||
# Ensure it's a password-only user
|
||||
{:ok, password_user} = Ash.reload(password_user)
|
||||
{:ok, password_user} = Ash.reload(password_user, actor: actor)
|
||||
assert not is_nil(password_user.hashed_password)
|
||||
# Force oidc_id to be nil to avoid any confusion
|
||||
{:ok, password_user} =
|
||||
password_user
|
||||
|> Ash.Changeset.for_update(:update, %{})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, nil)
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
assert is_nil(password_user.oidc_id)
|
||||
|
||||
|
|
@ -124,10 +135,13 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should fail with email update conflict error
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
|
@ -150,7 +164,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
end
|
||||
|
||||
describe "OIDC user updates email to email of different OIDC user" do
|
||||
test "should fail with clear error message about different OIDC account" do
|
||||
test "should fail with clear error message about different OIDC account", %{actor: actor} do
|
||||
# Create first OIDC user
|
||||
{:ok, _oidc_user1} =
|
||||
Mv.Accounts.User
|
||||
|
|
@ -158,7 +172,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
email: "oidcuser1@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_aaa")
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# Create second OIDC user with target email
|
||||
{:ok, _oidc_user2} =
|
||||
|
|
@ -167,7 +181,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
email: "oidcuser2@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_bbb")
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# First OIDC user tries to update email to second user's email
|
||||
user_info = %{
|
||||
|
|
@ -176,10 +190,13 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should fail with "already linked to different OIDC account" error
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
|
@ -201,14 +218,14 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
end
|
||||
|
||||
describe "New OIDC user registration scenarios (for comparison)" do
|
||||
test "new OIDC user with email of passwordless user triggers linking flow" do
|
||||
test "new OIDC user with email of passwordless user triggers linking flow", %{actor: actor} do
|
||||
# Create passwordless user
|
||||
{:ok, passwordless_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Changeset.for_create(:create_user, %{
|
||||
email: "passwordless@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# New OIDC user tries to register
|
||||
user_info = %{
|
||||
|
|
@ -217,10 +234,13 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should trigger PasswordVerificationRequired (linking flow)
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
|
@ -234,7 +254,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "new OIDC user with email of existing OIDC user shows hard error" do
|
||||
test "new OIDC user with email of existing OIDC user shows hard error", %{actor: actor} do
|
||||
# Create existing OIDC user
|
||||
{:ok, _existing_oidc_user} =
|
||||
Mv.Accounts.User
|
||||
|
|
@ -242,7 +262,7 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
email: "existing@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_existing")
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# New OIDC user tries to register with same email
|
||||
user_info = %{
|
||||
|
|
@ -251,10 +271,13 @@ defmodule MvWeb.OidcEmailUpdateTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should fail with "already linked to different OIDC account" error
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
# Test OIDC callback scenarios by directly calling the actions
|
||||
# This simulates what happens during real OIDC authentication
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
describe "OIDC sign-in scenarios" do
|
||||
test "existing OIDC user with unchanged email can sign in" do
|
||||
# Create user with OIDC ID
|
||||
|
|
@ -20,11 +25,16 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
}
|
||||
|
||||
# Test sign_in_with_rauthy action directly
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
{:ok, [found_user]} =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert found_user.id == user.id
|
||||
assert to_string(found_user.email) == "existing@example.com"
|
||||
|
|
@ -39,10 +49,15 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
}
|
||||
|
||||
# Test register_with_rauthy action
|
||||
case Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
}) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
case Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
) do
|
||||
{:ok, new_user} ->
|
||||
assert to_string(new_user.email) == "newuser@example.com"
|
||||
assert new_user.oidc_id == "brand_new_oidc_456"
|
||||
|
|
@ -73,11 +88,16 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
}
|
||||
|
||||
# Should NOT find any user (security requirement)
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
result =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
# Either returns empty list OR authentication error - both mean "user not found"
|
||||
case result do
|
||||
|
|
@ -107,11 +127,16 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
"preferred_username" => "oidc.user@example.com"
|
||||
}
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
{:ok, [found_user]} =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: correct_user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: correct_user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert found_user.id == user.id
|
||||
|
||||
|
|
@ -122,10 +147,13 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: wrong_user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: wrong_user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
# Either returns empty list OR authentication error - both mean "user not found"
|
||||
case result do
|
||||
|
|
@ -154,11 +182,16 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
"preferred_username" => "empty.oidc@example.com"
|
||||
}
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
result =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
# Either returns empty list OR authentication error - both mean "user not found"
|
||||
case result do
|
||||
|
|
@ -189,11 +222,16 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
"preferred_username" => "conflict@example.com"
|
||||
}
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
# Should fail with hard error (not PasswordVerificationRequired)
|
||||
# This prevents someone with OIDC provider B from taking over an account
|
||||
|
|
@ -220,11 +258,16 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
"preferred_username" => "nosub@example.com"
|
||||
}
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert {:error,
|
||||
%Ash.Error.Invalid{
|
||||
|
|
@ -239,11 +282,16 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
"sub" => "noemail_oidc_123"
|
||||
}
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
||||
|
|
@ -264,11 +312,16 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
"preferred_username" => "new@example.com"
|
||||
}
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
{:ok, user} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert user.id == existing_user.id
|
||||
assert to_string(user.email) == "new@example.com"
|
||||
|
|
@ -281,11 +334,16 @@ defmodule MvWeb.OidcIntegrationTest do
|
|||
"preferred_username" => "altid@example.com"
|
||||
}
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
{:ok, user} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert user.oidc_id == "alt_oidc_id_123"
|
||||
assert to_string(user.email) == "altid@example.com"
|
||||
|
|
|
|||
|
|
@ -8,9 +8,15 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
use MvWeb.ConnCase, async: true
|
||||
require Ash.Query
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
describe "OIDC login with existing email (no oidc_id) - Email Collision" do
|
||||
@tag :test_proposal
|
||||
test "OIDC register with existing password user email fails with PasswordVerificationRequired" do
|
||||
test "OIDC register with existing password user email fails with PasswordVerificationRequired",
|
||||
%{actor: actor} do
|
||||
# Create password-only user
|
||||
existing_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -26,10 +32,13 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should fail with PasswordVerificationRequired error
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
|
@ -47,7 +56,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
end
|
||||
|
||||
@tag :test_proposal
|
||||
test "PasswordVerificationRequired error contains necessary context" do
|
||||
test "PasswordVerificationRequired error contains necessary context", %{actor: actor} do
|
||||
existing_user =
|
||||
create_test_user(%{
|
||||
email: "test@example.com",
|
||||
|
|
@ -61,10 +70,13 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
}
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
password_error =
|
||||
Enum.find(errors, fn err ->
|
||||
|
|
@ -78,7 +90,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
end
|
||||
|
||||
@tag :test_proposal
|
||||
test "after successful password verification, oidc_id can be set" do
|
||||
test "after successful password verification, oidc_id can be set", %{actor: actor} do
|
||||
# Create password user
|
||||
user =
|
||||
create_test_user(%{
|
||||
|
|
@ -97,12 +109,12 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
{:ok, updated_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Query.filter(id == ^user.id)
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: actor)
|
||||
|> Ash.Changeset.for_update(:link_oidc_id, %{
|
||||
oidc_id: user_info["sub"],
|
||||
oidc_user_info: user_info
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
assert updated_user.id == user.id
|
||||
assert updated_user.oidc_id == "linked_oidc_555"
|
||||
|
|
@ -112,7 +124,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
end
|
||||
|
||||
@tag :test_proposal
|
||||
test "password verification with wrong password keeps oidc_id as nil" do
|
||||
test "password verification with wrong password keeps oidc_id as nil", %{actor: actor} do
|
||||
# This test verifies that if password verification fails,
|
||||
# the oidc_id should NOT be set
|
||||
|
||||
|
|
@ -131,7 +143,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
# before link_oidc_id is called, so here we just verify the user state
|
||||
|
||||
# User should still have no oidc_id (no linking happened)
|
||||
{:ok, unchanged_user} = Ash.get(Mv.Accounts.User, user.id)
|
||||
{:ok, unchanged_user} = Ash.get(Mv.Accounts.User, user.id, actor: actor)
|
||||
assert is_nil(unchanged_user.oidc_id)
|
||||
assert unchanged_user.hashed_password == user.hashed_password
|
||||
end
|
||||
|
|
@ -139,7 +151,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
|
||||
describe "OIDC login with email of user having different oidc_id - Account Conflict" do
|
||||
@tag :test_proposal
|
||||
test "OIDC register with email of user having different oidc_id fails" do
|
||||
test "OIDC register with email of user having different oidc_id fails", %{actor: actor} do
|
||||
# User already linked to OIDC provider A
|
||||
_existing_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -155,10 +167,13 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should fail - cannot link different OIDC account to same email
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
|
@ -171,7 +186,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
end
|
||||
|
||||
@tag :test_proposal
|
||||
test "existing OIDC user email remains unchanged when oidc_id matches" do
|
||||
test "existing OIDC user email remains unchanged when oidc_id matches", %{actor: actor} do
|
||||
user =
|
||||
create_test_user(%{
|
||||
email: "oidc@example.com",
|
||||
|
|
@ -186,10 +201,13 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
|
||||
# This should work via upsert
|
||||
{:ok, updated_user} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert updated_user.id == user.id
|
||||
assert updated_user.oidc_id == "oidc_stable_789"
|
||||
|
|
@ -199,7 +217,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
|
||||
describe "Email update during OIDC linking" do
|
||||
@tag :test_proposal
|
||||
test "linking OIDC to password account updates email if different in OIDC" do
|
||||
test "linking OIDC to password account updates email if different in OIDC", %{actor: actor} do
|
||||
# Password user with old email
|
||||
user =
|
||||
create_test_user(%{
|
||||
|
|
@ -218,19 +236,19 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
{:ok, updated_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Query.filter(id == ^user.id)
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: actor)
|
||||
|> Ash.Changeset.for_update(:link_oidc_id, %{
|
||||
oidc_id: user_info["sub"],
|
||||
oidc_user_info: user_info
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
assert updated_user.oidc_id == "oidc_link_999"
|
||||
assert to_string(updated_user.email) == "newemail@example.com"
|
||||
end
|
||||
|
||||
@tag :test_proposal
|
||||
test "email change during linking triggers member email sync" do
|
||||
test "email change during linking triggers member email sync", %{actor: actor} do
|
||||
# Create member
|
||||
member =
|
||||
Ash.Seed.seed!(Mv.Membership.Member, %{
|
||||
|
|
@ -257,25 +275,25 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
{:ok, updated_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Query.filter(id == ^user.id)
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: actor)
|
||||
|> Ash.Changeset.for_update(:link_oidc_id, %{
|
||||
oidc_id: user_info["sub"],
|
||||
oidc_user_info: user_info
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# Verify user email changed
|
||||
assert to_string(updated_user.email) == "newemail@example.com"
|
||||
|
||||
# Verify member email was synced
|
||||
{:ok, updated_member} = Ash.get(Mv.Membership.Member, member.id)
|
||||
{:ok, updated_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
|
||||
assert to_string(updated_member.email) == "newemail@example.com"
|
||||
end
|
||||
end
|
||||
|
||||
describe "Edge cases" do
|
||||
@tag :test_proposal
|
||||
test "user with empty string oidc_id is treated as password-only user" do
|
||||
test "user with empty string oidc_id is treated as password-only user", %{actor: actor} do
|
||||
_user =
|
||||
create_test_user(%{
|
||||
email: "empty@example.com",
|
||||
|
|
@ -290,10 +308,13 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should trigger PasswordVerificationRequired (empty string = no OIDC)
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
|
@ -307,7 +328,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
end
|
||||
|
||||
@tag :test_proposal
|
||||
test "cannot link same oidc_id to multiple users" do
|
||||
test "cannot link same oidc_id to multiple users", %{actor: actor} do
|
||||
# User 1 with OIDC
|
||||
_user1 =
|
||||
create_test_user(%{
|
||||
|
|
@ -323,7 +344,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
email: "user2@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "shared_oidc_333")
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# Should fail due to unique constraint on oidc_id
|
||||
assert match?({:error, %Ash.Error.Invalid{}}, result)
|
||||
|
|
@ -337,14 +358,16 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
end
|
||||
|
||||
describe "OIDC login with passwordless user - Requires Linking Flow" do
|
||||
test "user without password and without oidc_id triggers PasswordVerificationRequired" do
|
||||
test "user without password and without oidc_id triggers PasswordVerificationRequired", %{
|
||||
actor: actor
|
||||
} do
|
||||
# Create user without password (e.g., invited user)
|
||||
{:ok, existing_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Changeset.for_create(:create_user, %{
|
||||
email: "invited@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# Verify user has no password and no oidc_id
|
||||
assert is_nil(existing_user.hashed_password)
|
||||
|
|
@ -372,14 +395,14 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "user without password but WITH password later requires verification" do
|
||||
test "user without password but WITH password later requires verification", %{actor: actor} do
|
||||
# Create user without password first
|
||||
{:ok, user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Changeset.for_create(:create_user, %{
|
||||
email: "added-password@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# User sets password later (using admin action)
|
||||
{:ok, user_with_password} =
|
||||
|
|
@ -387,7 +410,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
|> Ash.Changeset.for_update(:admin_set_password, %{
|
||||
password: "newpassword123"
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
assert not is_nil(user_with_password.hashed_password)
|
||||
|
||||
|
|
@ -398,10 +421,13 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should fail with PasswordVerificationRequired
|
||||
assert {:error, %Ash.Error.Invalid{}} = result
|
||||
|
|
@ -414,7 +440,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
end
|
||||
|
||||
describe "OIDC login with different oidc_id - Hard Error" do
|
||||
test "user with different oidc_id cannot be linked (hard error)" do
|
||||
test "user with different oidc_id cannot be linked (hard error)", %{actor: actor} do
|
||||
# Create user with existing OIDC ID
|
||||
{:ok, existing_user} =
|
||||
Mv.Accounts.User
|
||||
|
|
@ -422,7 +448,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
email: "already-linked@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "original_oidc_999")
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
assert existing_user.oidc_id == "original_oidc_999"
|
||||
|
||||
|
|
@ -433,10 +459,13 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should fail with hard error (not PasswordVerificationRequired)
|
||||
assert {:error, %Ash.Error.Invalid{}} = result
|
||||
|
|
@ -459,7 +488,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "cannot link different oidc_id even with password verification" do
|
||||
test "cannot link different oidc_id even with password verification", %{actor: actor} do
|
||||
# Create user with password AND existing OIDC ID
|
||||
existing_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -478,10 +507,13 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should fail - cannot link different OIDC ID
|
||||
assert {:error, %Ash.Error.Invalid{}} = result
|
||||
|
|
|
|||
|
|
@ -7,15 +7,20 @@ defmodule MvWeb.OidcPasswordlessLinkingTest do
|
|||
"""
|
||||
use MvWeb.ConnCase, async: true
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
describe "Passwordless user - Automatic linking via special action" do
|
||||
test "passwordless user can be linked via link_passwordless_oidc action" do
|
||||
test "passwordless user can be linked via link_passwordless_oidc action", %{actor: actor} do
|
||||
# Create user without password (e.g., invited user)
|
||||
{:ok, existing_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Changeset.for_create(:create_user, %{
|
||||
email: "invited@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# Verify user has no password and no oidc_id
|
||||
assert is_nil(existing_user.hashed_password)
|
||||
|
|
@ -31,7 +36,7 @@ defmodule MvWeb.OidcPasswordlessLinkingTest do
|
|||
"preferred_username" => "invited@example.com"
|
||||
}
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# User should now have oidc_id linked
|
||||
assert linked_user.oidc_id == "auto_link_oidc_123"
|
||||
|
|
@ -47,20 +52,22 @@ defmodule MvWeb.OidcPasswordlessLinkingTest do
|
|||
},
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
|> Ash.read_one()
|
||||
|> Ash.read_one(actor: actor)
|
||||
|
||||
assert {:ok, signed_in_user} = result
|
||||
assert signed_in_user.id == existing_user.id
|
||||
end
|
||||
|
||||
test "passwordless user triggers PasswordVerificationRequired for linking flow" do
|
||||
test "passwordless user triggers PasswordVerificationRequired for linking flow", %{
|
||||
actor: actor
|
||||
} do
|
||||
# Create passwordless user
|
||||
{:ok, existing_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Changeset.for_create(:create_user, %{
|
||||
email: "passwordless@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
assert is_nil(existing_user.hashed_password)
|
||||
assert is_nil(existing_user.oidc_id)
|
||||
|
|
@ -95,7 +102,7 @@ defmodule MvWeb.OidcPasswordlessLinkingTest do
|
|||
end
|
||||
|
||||
describe "User with different OIDC ID - Hard Error" do
|
||||
test "user with different oidc_id gets hard error, not password verification" do
|
||||
test "user with different oidc_id gets hard error, not password verification", %{actor: actor} do
|
||||
# Create user with existing OIDC ID
|
||||
{:ok, _existing_user} =
|
||||
Mv.Accounts.User
|
||||
|
|
@ -103,7 +110,7 @@ defmodule MvWeb.OidcPasswordlessLinkingTest do
|
|||
email: "already-linked@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "original_oidc_999")
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# Try to register with same email but different OIDC ID
|
||||
user_info = %{
|
||||
|
|
@ -138,7 +145,7 @@ defmodule MvWeb.OidcPasswordlessLinkingTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "passwordless user with different oidc_id also gets hard error" do
|
||||
test "passwordless user with different oidc_id also gets hard error", %{actor: actor} do
|
||||
# Create passwordless user with OIDC ID
|
||||
{:ok, existing_user} =
|
||||
Mv.Accounts.User
|
||||
|
|
@ -146,7 +153,7 @@ defmodule MvWeb.OidcPasswordlessLinkingTest do
|
|||
email: "passwordless-linked@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "first_oidc_777")
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
assert is_nil(existing_user.hashed_password)
|
||||
assert existing_user.oidc_id == "first_oidc_777"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
alias MvWeb.Helpers.MembershipFeeHelpers
|
||||
alias Mv.MembershipFees.CalendarCycles
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
describe "format_currency/1" do
|
||||
test "formats decimal amount correctly" do
|
||||
assert MembershipFeeHelpers.format_currency(Decimal.new("60.00")) == "60,00 €"
|
||||
|
|
@ -63,7 +68,7 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
end
|
||||
|
||||
describe "get_last_completed_cycle/2" do
|
||||
test "returns last completed cycle for member" do
|
||||
test "returns last completed cycle for member", %{actor: actor} do
|
||||
# Create test data
|
||||
fee_type =
|
||||
Mv.MembershipFees.MembershipFeeType
|
||||
|
|
@ -72,7 +77,7 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
amount: Decimal.new("50.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Create member without fee type first to avoid auto-generation
|
||||
member =
|
||||
|
|
@ -83,21 +88,21 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
email: "test#{System.unique_integer([:positive])}@example.com",
|
||||
join_date: ~D[2022-01-01]
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Assign fee type after member creation (this may generate cycles, but we'll create our own)
|
||||
member =
|
||||
member
|
||||
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: actor)
|
||||
|
||||
# Delete any auto-generated cycles first
|
||||
cycles =
|
||||
Mv.MembershipFees.MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: actor)
|
||||
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle) end)
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle, actor: actor) end)
|
||||
|
||||
# Create cycles manually
|
||||
_cycle_2022 =
|
||||
|
|
@ -109,7 +114,7 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
membership_fee_type_id: fee_type.id,
|
||||
status: :paid
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
cycle_2023 =
|
||||
Mv.MembershipFees.MembershipFeeCycle
|
||||
|
|
@ -120,7 +125,7 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
membership_fee_type_id: fee_type.id,
|
||||
status: :paid
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Load cycles with membership_fee_type relationship
|
||||
member =
|
||||
|
|
@ -135,7 +140,7 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
assert last_cycle.id == cycle_2023.id
|
||||
end
|
||||
|
||||
test "returns nil if no cycles exist" do
|
||||
test "returns nil if no cycles exist", %{actor: actor} do
|
||||
fee_type =
|
||||
Mv.MembershipFees.MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
|
|
@ -143,7 +148,7 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
amount: Decimal.new("50.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Create member without fee type first
|
||||
member =
|
||||
|
|
@ -153,21 +158,21 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
last_name: "Member",
|
||||
email: "test#{System.unique_integer([:positive])}@example.com"
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Assign fee type
|
||||
member =
|
||||
member
|
||||
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: actor)
|
||||
|
||||
# Delete any auto-generated cycles
|
||||
cycles =
|
||||
Mv.MembershipFees.MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: actor)
|
||||
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle) end)
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle, actor: actor) end)
|
||||
|
||||
# Load cycles and fee type (will be empty)
|
||||
member =
|
||||
|
|
@ -181,7 +186,7 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
end
|
||||
|
||||
describe "get_current_cycle/2" do
|
||||
test "returns current cycle for member" do
|
||||
test "returns current cycle for member", %{actor: actor} do
|
||||
fee_type =
|
||||
Mv.MembershipFees.MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
|
|
@ -189,7 +194,7 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
amount: Decimal.new("50.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Create member without fee type first
|
||||
member =
|
||||
|
|
@ -200,21 +205,21 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
email: "test#{System.unique_integer([:positive])}@example.com",
|
||||
join_date: ~D[2023-01-01]
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Assign fee type
|
||||
member =
|
||||
member
|
||||
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: actor)
|
||||
|
||||
# Delete any auto-generated cycles
|
||||
cycles =
|
||||
Mv.MembershipFees.MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: actor)
|
||||
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle) end)
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle, actor: actor) end)
|
||||
|
||||
today = Date.utc_today()
|
||||
current_year_start = %{today | month: 1, day: 1}
|
||||
|
|
@ -228,7 +233,7 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
membership_fee_type_id: fee_type.id,
|
||||
status: :unpaid
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Load cycles with membership_fee_type relationship
|
||||
member =
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
|||
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create admin user for testing
|
||||
{:ok, user} =
|
||||
Mv.Accounts.User
|
||||
|
|
@ -26,7 +28,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
|||
email: "admin#{System.unique_integer([:positive])}@mv.local",
|
||||
password: "testpassword123"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
conn = log_in_user(build_conn(), user)
|
||||
%{conn: conn, user: user}
|
||||
|
|
@ -156,14 +158,16 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
|||
# Should show success message
|
||||
assert render(view) =~ "Data field deleted successfully"
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Custom field should be gone from database
|
||||
assert {:error, _} = Ash.get(CustomField, custom_field.id)
|
||||
assert {:error, _} = Ash.get(CustomField, custom_field.id, actor: system_actor)
|
||||
|
||||
# Custom field value should also be gone (CASCADE)
|
||||
assert {:error, _} = Ash.get(CustomFieldValue, custom_field_value.id)
|
||||
assert {:error, _} = Ash.get(CustomFieldValue, custom_field_value.id, actor: system_actor)
|
||||
|
||||
# Member should still exist
|
||||
assert {:ok, _} = Ash.get(Member, member.id)
|
||||
assert {:ok, _} = Ash.get(Member, member.id, actor: system_actor)
|
||||
end
|
||||
|
||||
test "button remains disabled and custom field not deleted when slug doesn't match", %{
|
||||
|
|
@ -188,7 +192,8 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
|||
assert html =~ ~r/disabled(?:=""|(?!\w))/
|
||||
|
||||
# Custom field should still exist since deletion couldn't proceed
|
||||
assert {:ok, _} = Ash.get(CustomField, custom_field.id)
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
assert {:ok, _} = Ash.get(CustomField, custom_field.id, actor: system_actor)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -214,38 +219,45 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
|||
refute has_element?(view, "#delete-custom-field-modal")
|
||||
|
||||
# Custom field should still exist
|
||||
assert {:ok, _} = Ash.get(CustomField, custom_field.id)
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
assert {:ok, _} = Ash.get(CustomField, custom_field.id, actor: system_actor)
|
||||
end
|
||||
end
|
||||
|
||||
# Helper functions
|
||||
defp create_member do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Test",
|
||||
last_name: "User#{System.unique_integer([:positive])}",
|
||||
email: "test#{System.unique_integer([:positive])}@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
end
|
||||
|
||||
defp create_custom_field(name, value_type) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "#{name}_#{System.unique_integer([:positive])}",
|
||||
value_type: value_type
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
end
|
||||
|
||||
defp create_custom_field_value(member, custom_field, value) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member.id,
|
||||
custom_field_id: custom_field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => value}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
end
|
||||
|
||||
defp log_in_user(conn, user) do
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
|
|||
require Ash.Query
|
||||
|
||||
setup %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create admin user
|
||||
{:ok, user} =
|
||||
Mv.Accounts.User
|
||||
|
|
@ -19,7 +21,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
|
|||
email: "admin#{System.unique_integer([:positive])}@mv.local",
|
||||
password: "testpassword123"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
authenticated_conn = conn_with_password_user(conn, user)
|
||||
%{conn: authenticated_conn, user: user}
|
||||
|
|
@ -27,6 +29,8 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
|
|||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -37,11 +41,13 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
|
|||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
# Helper to create a member
|
||||
defp create_member(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
|
|
@ -52,7 +58,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
|
|||
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
describe "create form" do
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
use MvWeb.ConnCase, async: true
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
describe "profile navigation" do
|
||||
test "clicking profile button redirects to current user profile", %{conn: conn} do
|
||||
# Setup: Create and login a user
|
||||
|
|
@ -60,7 +65,7 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
end
|
||||
|
||||
describe "profile navigation with OIDC user" do
|
||||
test "shows correct profile data for OIDC user", %{conn: conn} do
|
||||
test "shows correct profile data for OIDC user", %{conn: conn, actor: actor} do
|
||||
# Setup: Create OIDC user with sub claim
|
||||
user_info = %{
|
||||
"sub" => "oidc_123",
|
||||
|
|
@ -78,7 +83,7 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
user_info: user_info,
|
||||
oauth_tokens: oauth_tokens
|
||||
})
|
||||
|> Ash.create!(domain: Mv.Accounts)
|
||||
|> Ash.create!(domain: Mv.Accounts, actor: actor)
|
||||
|
||||
# Login user via OIDC
|
||||
conn = sign_in_user_via_oidc(conn, user)
|
||||
|
|
@ -94,7 +99,10 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
assert html =~ "Not enabled"
|
||||
end
|
||||
|
||||
test "profile navigation works across different authentication methods", %{conn: conn} do
|
||||
test "profile navigation works across different authentication methods", %{
|
||||
conn: conn,
|
||||
actor: actor
|
||||
} do
|
||||
# Create password user
|
||||
password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -119,7 +127,7 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
user_info: user_info,
|
||||
oauth_tokens: oauth_tokens
|
||||
})
|
||||
|> Ash.create!(domain: Mv.Accounts)
|
||||
|> Ash.create!(domain: Mv.Accounts, actor: actor)
|
||||
|
||||
# Test with password user
|
||||
conn_password = conn_with_password_user(conn, password_user)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ defmodule MvWeb.RoleLive.ShowTest do
|
|||
end
|
||||
|
||||
# Helper to create admin user with admin role
|
||||
defp create_admin_user(conn) do
|
||||
defp create_admin_user(conn, actor) do
|
||||
# Create admin role
|
||||
admin_role =
|
||||
case Authorization.list_roles() do
|
||||
|
|
@ -69,14 +69,14 @@ defmodule MvWeb.RoleLive.ShowTest do
|
|||
email: "admin#{System.unique_integer([:positive])}@mv.local",
|
||||
password: "testpassword123"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# Assign admin role using manage_relationship
|
||||
{:ok, user} =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:update, %{})
|
||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# Load role for authorization checks (must be loaded for can?/3 to work)
|
||||
user_with_role = Ash.load!(user, :role, domain: Mv.Accounts)
|
||||
|
|
@ -88,8 +88,9 @@ defmodule MvWeb.RoleLive.ShowTest do
|
|||
|
||||
describe "mount and display" do
|
||||
setup %{conn: conn} do
|
||||
{conn, _user, _admin_role} = create_admin_user(conn)
|
||||
%{conn: conn}
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{conn, _user, _admin_role} = create_admin_user(conn, system_actor)
|
||||
%{conn: conn, actor: system_actor}
|
||||
end
|
||||
|
||||
test "mounts successfully with valid role ID", %{conn: conn} do
|
||||
|
|
@ -135,7 +136,7 @@ defmodule MvWeb.RoleLive.ShowTest do
|
|||
assert html =~ gettext("Permission Set")
|
||||
end
|
||||
|
||||
test "displays system role badge when is_system_role is true", %{conn: conn} do
|
||||
test "displays system role badge when is_system_role is true", %{conn: conn, actor: actor} do
|
||||
system_role =
|
||||
Role
|
||||
|> Ash.Changeset.for_create(:create_role, %{
|
||||
|
|
@ -143,7 +144,7 @@ defmodule MvWeb.RoleLive.ShowTest do
|
|||
permission_set_name: "own_data"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:is_system_role, true)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
{:ok, _view, html} = live(conn, "/admin/roles/#{system_role.id}")
|
||||
|
||||
|
|
@ -172,8 +173,9 @@ defmodule MvWeb.RoleLive.ShowTest do
|
|||
|
||||
describe "navigation" do
|
||||
setup %{conn: conn} do
|
||||
{conn, _user, _admin_role} = create_admin_user(conn)
|
||||
%{conn: conn}
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{conn, _user, _admin_role} = create_admin_user(conn, system_actor)
|
||||
%{conn: conn, actor: system_actor}
|
||||
end
|
||||
|
||||
test "back button navigates to role list", %{conn: conn} do
|
||||
|
|
@ -209,8 +211,9 @@ defmodule MvWeb.RoleLive.ShowTest do
|
|||
|
||||
describe "error handling" do
|
||||
setup %{conn: conn} do
|
||||
{conn, _user, _admin_role} = create_admin_user(conn)
|
||||
%{conn: conn}
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{conn, _user, _admin_role} = create_admin_user(conn, system_actor)
|
||||
%{conn: conn, actor: system_actor}
|
||||
end
|
||||
|
||||
test "redirects to role list with error for invalid role ID", %{conn: conn} do
|
||||
|
|
@ -226,11 +229,12 @@ defmodule MvWeb.RoleLive.ShowTest do
|
|||
|
||||
describe "delete functionality" do
|
||||
setup %{conn: conn} do
|
||||
{conn, _user, _admin_role} = create_admin_user(conn)
|
||||
%{conn: conn}
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{conn, _user, _admin_role} = create_admin_user(conn, system_actor)
|
||||
%{conn: conn, actor: system_actor}
|
||||
end
|
||||
|
||||
test "delete button is not shown for system roles", %{conn: conn} do
|
||||
test "delete button is not shown for system roles", %{conn: conn, actor: actor} do
|
||||
system_role =
|
||||
Role
|
||||
|> Ash.Changeset.for_create(:create_role, %{
|
||||
|
|
@ -238,7 +242,7 @@ defmodule MvWeb.RoleLive.ShowTest do
|
|||
permission_set_name: "own_data"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:is_system_role, true)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
{:ok, _view, html} = live(conn, "/admin/roles/#{system_role.id}")
|
||||
|
||||
|
|
@ -258,8 +262,9 @@ defmodule MvWeb.RoleLive.ShowTest do
|
|||
|
||||
describe "page title" do
|
||||
setup %{conn: conn} do
|
||||
{conn, _user, _admin_role} = create_admin_user(conn)
|
||||
%{conn: conn}
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{conn, _user, _admin_role} = create_admin_user(conn, system_actor)
|
||||
%{conn: conn, actor: system_actor}
|
||||
end
|
||||
|
||||
test "sets correct page title", %{conn: conn} do
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ defmodule MvWeb.RoleLiveTest do
|
|||
end
|
||||
|
||||
# Helper to create admin user with admin role
|
||||
defp create_admin_user(conn) do
|
||||
defp create_admin_user(conn, actor) do
|
||||
# Create admin role
|
||||
admin_role =
|
||||
case Authorization.list_roles() do
|
||||
|
|
@ -60,14 +60,14 @@ defmodule MvWeb.RoleLiveTest do
|
|||
email: "admin#{System.unique_integer([:positive])}@mv.local",
|
||||
password: "testpassword123"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# Assign admin role using manage_relationship
|
||||
{:ok, user} =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:update, %{})
|
||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# Load role for authorization checks (must be loaded for can?/3 to work)
|
||||
user_with_role = Ash.load!(user, :role, domain: Mv.Accounts)
|
||||
|
|
@ -78,14 +78,14 @@ defmodule MvWeb.RoleLiveTest do
|
|||
end
|
||||
|
||||
# Helper to create non-admin user
|
||||
defp create_non_admin_user(conn) do
|
||||
defp create_non_admin_user(conn, actor) do
|
||||
{:ok, user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Changeset.for_create(:register_with_password, %{
|
||||
email: "user#{System.unique_integer([:positive])}@mv.local",
|
||||
password: "testpassword123"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
conn = conn_with_password_user(conn, user)
|
||||
{conn, user}
|
||||
|
|
@ -93,8 +93,9 @@ defmodule MvWeb.RoleLiveTest do
|
|||
|
||||
describe "index page" do
|
||||
setup %{conn: conn} do
|
||||
{conn, user, _admin_role} = create_admin_user(conn)
|
||||
%{conn: conn, user: user}
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{conn, user, _admin_role} = create_admin_user(conn, system_actor)
|
||||
%{conn: conn, actor: system_actor, user: user}
|
||||
end
|
||||
|
||||
test "mounts successfully", %{conn: conn} do
|
||||
|
|
@ -121,7 +122,7 @@ defmodule MvWeb.RoleLiveTest do
|
|||
assert html =~ role.permission_set_name
|
||||
end
|
||||
|
||||
test "shows system role badge", %{conn: conn} do
|
||||
test "shows system role badge", %{conn: conn, actor: actor} do
|
||||
_system_role =
|
||||
Role
|
||||
|> Ash.Changeset.for_create(:create_role, %{
|
||||
|
|
@ -129,14 +130,14 @@ defmodule MvWeb.RoleLiveTest do
|
|||
permission_set_name: "own_data"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:is_system_role, true)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
{:ok, _view, html} = live(conn, "/admin/roles")
|
||||
|
||||
assert html =~ "System Role" || html =~ "system"
|
||||
end
|
||||
|
||||
test "delete button disabled for system roles", %{conn: conn} do
|
||||
test "delete button disabled for system roles", %{conn: conn, actor: actor} do
|
||||
system_role =
|
||||
Role
|
||||
|> Ash.Changeset.for_create(:create_role, %{
|
||||
|
|
@ -144,7 +145,7 @@ defmodule MvWeb.RoleLiveTest do
|
|||
permission_set_name: "own_data"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:is_system_role, true)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
{:ok, view, _html} = live(conn, "/admin/roles")
|
||||
|
||||
|
|
@ -191,8 +192,9 @@ defmodule MvWeb.RoleLiveTest do
|
|||
|
||||
describe "show page" do
|
||||
setup %{conn: conn} do
|
||||
{conn, user, _admin_role} = create_admin_user(conn)
|
||||
%{conn: conn, user: user}
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{conn, user, _admin_role} = create_admin_user(conn, system_actor)
|
||||
%{conn: conn, actor: system_actor, user: user}
|
||||
end
|
||||
|
||||
test "mounts with valid role ID", %{conn: conn} do
|
||||
|
|
@ -215,7 +217,7 @@ defmodule MvWeb.RoleLiveTest do
|
|||
assert match?({:error, {:redirect, %{to: "/admin/roles"}}}, result)
|
||||
end
|
||||
|
||||
test "shows system role badge if is_system_role is true", %{conn: conn} do
|
||||
test "shows system role badge if is_system_role is true", %{conn: conn, actor: actor} do
|
||||
system_role =
|
||||
Role
|
||||
|> Ash.Changeset.for_create(:create_role, %{
|
||||
|
|
@ -223,7 +225,7 @@ defmodule MvWeb.RoleLiveTest do
|
|||
permission_set_name: "own_data"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:is_system_role, true)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
{:ok, _view, html} = live(conn, "/admin/roles/#{system_role.id}")
|
||||
|
||||
|
|
@ -233,8 +235,9 @@ defmodule MvWeb.RoleLiveTest do
|
|||
|
||||
describe "form - create" do
|
||||
setup %{conn: conn} do
|
||||
{conn, user, _admin_role} = create_admin_user(conn)
|
||||
%{conn: conn, user: user}
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{conn, user, _admin_role} = create_admin_user(conn, system_actor)
|
||||
%{conn: conn, actor: system_actor, user: user}
|
||||
end
|
||||
|
||||
test "mounts successfully", %{conn: conn} do
|
||||
|
|
@ -306,9 +309,10 @@ defmodule MvWeb.RoleLiveTest do
|
|||
|
||||
describe "form - edit" do
|
||||
setup %{conn: conn} do
|
||||
{conn, user, _admin_role} = create_admin_user(conn)
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{conn, user, _admin_role} = create_admin_user(conn, system_actor)
|
||||
role = create_role()
|
||||
%{conn: conn, user: user, role: role}
|
||||
%{conn: conn, actor: system_actor, user: user, role: role}
|
||||
end
|
||||
|
||||
test "mounts with valid role ID", %{conn: conn, role: role} do
|
||||
|
|
@ -347,7 +351,7 @@ defmodule MvWeb.RoleLiveTest do
|
|||
assert updated_role.name == "Updated Role Name"
|
||||
end
|
||||
|
||||
test "updates system role's permission_set_name", %{conn: conn} do
|
||||
test "updates system role's permission_set_name", %{conn: conn, actor: actor} do
|
||||
system_role =
|
||||
Role
|
||||
|> Ash.Changeset.for_create(:create_role, %{
|
||||
|
|
@ -355,7 +359,7 @@ defmodule MvWeb.RoleLiveTest do
|
|||
permission_set_name: "own_data"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:is_system_role, true)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
{:ok, view, _html} = live(conn, "/admin/roles/#{system_role.id}/edit?return_to=show")
|
||||
|
||||
|
|
@ -379,8 +383,9 @@ defmodule MvWeb.RoleLiveTest do
|
|||
|
||||
describe "delete functionality" do
|
||||
setup %{conn: conn} do
|
||||
{conn, user, _admin_role} = create_admin_user(conn)
|
||||
%{conn: conn, user: user}
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{conn, user, _admin_role} = create_admin_user(conn, system_actor)
|
||||
%{conn: conn, actor: system_actor, user: user}
|
||||
end
|
||||
|
||||
test "deletes non-system role", %{conn: conn} do
|
||||
|
|
@ -400,7 +405,7 @@ defmodule MvWeb.RoleLiveTest do
|
|||
Authorization.get_role(role.id)
|
||||
end
|
||||
|
||||
test "fails to delete system role with error message", %{conn: conn} do
|
||||
test "fails to delete system role with error message", %{conn: conn, actor: actor} do
|
||||
system_role =
|
||||
Role
|
||||
|> Ash.Changeset.for_create(:create_role, %{
|
||||
|
|
@ -408,7 +413,7 @@ defmodule MvWeb.RoleLiveTest do
|
|||
permission_set_name: "own_data"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:is_system_role, true)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
{:ok, view, html} = live(conn, "/admin/roles")
|
||||
|
||||
|
|
@ -428,8 +433,13 @@ defmodule MvWeb.RoleLiveTest do
|
|||
end
|
||||
|
||||
describe "authorization" do
|
||||
test "only admin can access /admin/roles", %{conn: conn} do
|
||||
{conn, _user} = create_non_admin_user(conn)
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
test "only admin can access /admin/roles", %{conn: conn, actor: actor} do
|
||||
{conn, _user} = create_non_admin_user(conn, actor)
|
||||
|
||||
# Non-admin should be redirected or see error
|
||||
# Note: Authorization is checked via can_access_page? which returns false
|
||||
|
|
@ -443,8 +453,8 @@ defmodule MvWeb.RoleLiveTest do
|
|||
assert html =~ "Listing Roles" || html =~ "Roles"
|
||||
end
|
||||
|
||||
test "admin can access /admin/roles", %{conn: conn} do
|
||||
{conn, _user, _admin_role} = create_admin_user(conn)
|
||||
test "admin can access /admin/roles", %{conn: conn, actor: actor} do
|
||||
{conn, _user, _admin_role} = create_admin_user(conn, actor)
|
||||
|
||||
{:ok, _view, _html} = live(conn, "/admin/roles")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ defmodule MvWeb.UserLive.ShowTest do
|
|||
end
|
||||
|
||||
test "displays linked member when present", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create member
|
||||
{:ok, member} =
|
||||
Member
|
||||
|
|
@ -72,7 +74,7 @@ defmodule MvWeb.UserLive.ShowTest do
|
|||
last_name: "Smith",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create user and link to member
|
||||
user = create_test_user(%{email: "user@example.com"})
|
||||
|
|
@ -81,7 +83,7 @@ defmodule MvWeb.UserLive.ShowTest do
|
|||
user
|
||||
|> Ash.Changeset.for_update(:update, %{})
|
||||
|> Ash.Changeset.manage_relationship(:member, member, type: :append_and_remove)
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: system_actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, ~p"/users/#{user.id}")
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
|
|||
|
||||
describe "error handling - flash messages" do
|
||||
test "shows flash message when member creation fails with validation error", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create a member with the same email to trigger uniqueness error
|
||||
{:ok, _existing_member} =
|
||||
Member
|
||||
|
|
@ -20,7 +22,7 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
|
|||
last_name: "Member",
|
||||
email: "duplicate@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members/new")
|
||||
|
|
@ -73,6 +75,8 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
|
|||
end
|
||||
|
||||
test "shows flash message when member update fails", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create a member to edit
|
||||
{:ok, member} =
|
||||
Member
|
||||
|
|
@ -81,7 +85,7 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
|
|||
last_name: "Member",
|
||||
email: "original@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create another member with different email
|
||||
{:ok, _other_member} =
|
||||
|
|
@ -91,7 +95,7 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
|
|||
last_name: "Member",
|
||||
email: "other@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members/#{member.id}/edit")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
|
|||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -23,11 +25,13 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
|
|||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
# Helper to create a member
|
||||
defp create_member(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
|
|
@ -38,7 +42,7 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
|
|||
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
describe "membership fee type dropdown" do
|
||||
|
|
@ -123,10 +127,12 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
|
|||
|> render_submit()
|
||||
|
||||
# Verify member was created with fee type
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
member =
|
||||
Member
|
||||
|> Ash.Query.filter(email == ^form_data["member[email]"])
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: system_actor)
|
||||
|
||||
assert member.membership_fee_type_id == fee_type.id
|
||||
end
|
||||
|
|
@ -135,13 +141,14 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
|
|||
# Set default fee type in settings
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{:ok, settings} = Mv.Membership.get_settings()
|
||||
|
||||
settings
|
||||
|> Ash.Changeset.for_update(:update_membership_fee_settings, %{
|
||||
default_membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: system_actor)
|
||||
|
||||
{:ok, view, _html} = live(conn, "/members/new")
|
||||
|
||||
|
|
@ -156,6 +163,8 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
|
|||
conn: conn,
|
||||
current_user: admin_user
|
||||
} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create custom field
|
||||
custom_field =
|
||||
Mv.Membership.CustomField
|
||||
|
|
@ -164,7 +173,7 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
|
|||
value_type: :string,
|
||||
required: false
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
|
||||
# Create two fee types with same interval
|
||||
fee_type1 = create_fee_type(%{name: "Type 1", interval: :yearly})
|
||||
|
|
@ -250,6 +259,8 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
|
|||
end
|
||||
|
||||
test "removing custom field values works correctly", %{conn: conn, current_user: admin_user} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create custom field
|
||||
custom_field =
|
||||
Mv.Membership.CustomField
|
||||
|
|
@ -258,7 +269,7 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
|
|||
value_type: :string,
|
||||
required: false
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
|
|||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -23,11 +25,13 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
|
|||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
# Helper to create a member
|
||||
defp create_member(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
|
|
@ -38,13 +42,15 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
|
|||
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
# Helper to create a cycle
|
||||
# Note: Does not delete existing cycles - tests should manage their own test data
|
||||
# If cleanup is needed, it should be done in setup or explicitly in the test
|
||||
defp create_cycle(member, fee_type, attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
cycle_start: ~D[2023-01-01],
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -57,7 +63,7 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
|
|||
|
||||
MembershipFeeCycle
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
describe "load_cycles_for_members/2" do
|
||||
|
|
@ -75,7 +81,8 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
|
|||
|> Ash.Query.filter(id in [^member1.id, ^member2.id])
|
||||
|> MembershipFeeStatus.load_cycles_for_members()
|
||||
|
||||
members = Ash.read!(query)
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
members = Ash.read!(query, actor: system_actor)
|
||||
|
||||
assert length(members) == 2
|
||||
|
||||
|
|
@ -94,19 +101,21 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
|
|||
# Create member without fee type to avoid auto-generation
|
||||
member = create_member(%{})
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Assign fee type
|
||||
member =
|
||||
member
|
||||
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: system_actor)
|
||||
|
||||
# Delete any auto-generated cycles
|
||||
cycles =
|
||||
Mv.MembershipFees.MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: system_actor)
|
||||
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle) end)
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle, actor: system_actor) end)
|
||||
|
||||
# Create cycles with dates that ensure 2023 is last completed
|
||||
# Use a fixed "today" date in 2024 to make 2023 the last completed
|
||||
|
|
@ -137,19 +146,21 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
|
|||
# Create member without fee type to avoid auto-generation
|
||||
member = create_member(%{})
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Assign fee type
|
||||
member =
|
||||
member
|
||||
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: system_actor)
|
||||
|
||||
# Delete any auto-generated cycles
|
||||
cycles =
|
||||
Mv.MembershipFees.MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: system_actor)
|
||||
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle) end)
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle, actor: system_actor) end)
|
||||
|
||||
# Create cycles - use current year for current cycle
|
||||
today = Date.utc_today()
|
||||
|
|
@ -176,19 +187,21 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
|
|||
# Create member without fee type to avoid auto-generation
|
||||
member = create_member(%{})
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Assign fee type
|
||||
member =
|
||||
member
|
||||
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: system_actor)
|
||||
|
||||
# Delete any auto-generated cycles
|
||||
cycles =
|
||||
Mv.MembershipFees.MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: system_actor)
|
||||
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle) end)
|
||||
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle, actor: system_actor) end)
|
||||
|
||||
# Load cycles and fee type first (will be empty)
|
||||
member =
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do
|
|||
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create test member
|
||||
{:ok, member} =
|
||||
Member
|
||||
|
|
@ -22,7 +24,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do
|
|||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field with show_in_overview: true
|
||||
{:ok, field} =
|
||||
|
|
@ -32,7 +34,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field value
|
||||
{:ok, _cfv} =
|
||||
|
|
@ -42,7 +44,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do
|
|||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "A001"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
%{member: member, field: field}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create test members
|
||||
{:ok, member1} =
|
||||
Member
|
||||
|
|
@ -25,7 +27,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member2} =
|
||||
Member
|
||||
|
|
@ -34,7 +36,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
last_name: "Brown",
|
||||
email: "bob@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom fields
|
||||
{:ok, field_show_string} =
|
||||
|
|
@ -44,7 +46,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, field_hide} =
|
||||
CustomField
|
||||
|
|
@ -53,7 +55,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
value_type: :string,
|
||||
show_in_overview: false
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, field_show_integer} =
|
||||
CustomField
|
||||
|
|
@ -62,7 +64,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
value_type: :integer,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, field_show_boolean} =
|
||||
CustomField
|
||||
|
|
@ -71,7 +73,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
value_type: :boolean,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, field_show_date} =
|
||||
CustomField
|
||||
|
|
@ -80,7 +82,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
value_type: :date,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, field_show_email} =
|
||||
CustomField
|
||||
|
|
@ -89,7 +91,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
value_type: :email,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field values for member1
|
||||
{:ok, _cfv1} =
|
||||
|
|
@ -99,7 +101,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
custom_field_id: field_show_string.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "+49123456789"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv2} =
|
||||
CustomFieldValue
|
||||
|
|
@ -108,7 +110,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
custom_field_id: field_show_integer.id,
|
||||
value: %{"_union_type" => "integer", "_union_value" => 12_345}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv3} =
|
||||
CustomFieldValue
|
||||
|
|
@ -117,7 +119,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
custom_field_id: field_show_boolean.id,
|
||||
value: %{"_union_type" => "boolean", "_union_value" => true}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv4} =
|
||||
CustomFieldValue
|
||||
|
|
@ -126,7 +128,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
custom_field_id: field_show_date.id,
|
||||
value: %{"_union_type" => "date", "_union_value" => ~D[1990-05-15]}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv5} =
|
||||
CustomFieldValue
|
||||
|
|
@ -135,7 +137,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
custom_field_id: field_show_email.id,
|
||||
value: %{"_union_type" => "email", "_union_value" => "alice.private@example.com"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create hidden custom field value (should not be displayed)
|
||||
{:ok, _cfv_hidden} =
|
||||
|
|
@ -145,7 +147,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
|||
custom_field_id: field_hide.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Internal note"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
%{
|
||||
member1: member1,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
alias Mv.Membership.{CustomField, Member}
|
||||
|
||||
test "displays custom field column even when no members have values", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create test members without custom field values
|
||||
{:ok, _member1} =
|
||||
Member
|
||||
|
|
@ -21,7 +23,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _member2} =
|
||||
Member
|
||||
|
|
@ -30,7 +32,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
last_name: "Brown",
|
||||
email: "bob@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field with show_in_overview: true but no values
|
||||
{:ok, field} =
|
||||
|
|
@ -40,7 +42,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
|
@ -50,6 +52,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
end
|
||||
|
||||
test "displays very long custom field values correctly", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create test member
|
||||
{:ok, member} =
|
||||
Member
|
||||
|
|
@ -58,7 +62,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field
|
||||
{:ok, field} =
|
||||
|
|
@ -68,7 +72,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create very long value (but within limits)
|
||||
long_value = String.duplicate("A", 500)
|
||||
|
|
@ -80,7 +84,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => long_value}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
|
@ -91,6 +95,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
end
|
||||
|
||||
test "handles multiple custom fields with show_in_overview correctly", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create test member
|
||||
{:ok, member} =
|
||||
Member
|
||||
|
|
@ -99,7 +105,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create multiple custom fields with show_in_overview: true
|
||||
{:ok, field1} =
|
||||
|
|
@ -109,7 +115,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, field2} =
|
||||
CustomField
|
||||
|
|
@ -118,7 +124,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, field3} =
|
||||
CustomField
|
||||
|
|
@ -127,7 +133,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create values for all fields
|
||||
{:ok, _cfv1} =
|
||||
|
|
@ -137,7 +143,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
custom_field_id: field1.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Value1"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv2} =
|
||||
Mv.Membership.CustomFieldValue
|
||||
|
|
@ -146,7 +152,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
custom_field_id: field2.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Value2"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv3} =
|
||||
Mv.Membership.CustomFieldValue
|
||||
|
|
@ -155,7 +161,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
|||
custom_field_id: field3.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Value3"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create test members
|
||||
{:ok, member1} =
|
||||
Member
|
||||
|
|
@ -24,7 +26,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member2} =
|
||||
Member
|
||||
|
|
@ -33,7 +35,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Brown",
|
||||
email: "bob@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member3} =
|
||||
Member
|
||||
|
|
@ -42,7 +44,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Clark",
|
||||
email: "charlie@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field with show_in_overview: true
|
||||
{:ok, field_string} =
|
||||
|
|
@ -52,7 +54,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, field_integer} =
|
||||
CustomField
|
||||
|
|
@ -61,7 +63,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
value_type: :integer,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field values
|
||||
{:ok, _cfv1} =
|
||||
|
|
@ -71,7 +73,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field_string.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "A001"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv2} =
|
||||
CustomFieldValue
|
||||
|
|
@ -80,7 +82,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field_string.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "C003"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv3} =
|
||||
CustomFieldValue
|
||||
|
|
@ -89,7 +91,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field_string.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "B002"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv4} =
|
||||
CustomFieldValue
|
||||
|
|
@ -98,7 +100,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field_integer.id,
|
||||
value: %{"_union_type" => "integer", "_union_value" => 10}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv5} =
|
||||
CustomFieldValue
|
||||
|
|
@ -107,7 +109,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field_integer.id,
|
||||
value: %{"_union_type" => "integer", "_union_value" => 30}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv6} =
|
||||
CustomFieldValue
|
||||
|
|
@ -116,7 +118,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field_integer.id,
|
||||
value: %{"_union_type" => "integer", "_union_value" => 20}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
%{
|
||||
member1: member1,
|
||||
|
|
@ -236,6 +238,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
end
|
||||
|
||||
test "NULL values and empty strings are always sorted last (ASC)", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create additional members with NULL and empty string values
|
||||
{:ok, member_with_value} =
|
||||
Member
|
||||
|
|
@ -244,7 +248,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Test",
|
||||
email: "withvalue@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member_with_empty} =
|
||||
Member
|
||||
|
|
@ -253,7 +257,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Test",
|
||||
email: "withempty@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member_with_null} =
|
||||
Member
|
||||
|
|
@ -262,7 +266,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Test",
|
||||
email: "withnull@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member_with_another_value} =
|
||||
Member
|
||||
|
|
@ -271,7 +275,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Test",
|
||||
email: "another@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field
|
||||
{:ok, field} =
|
||||
|
|
@ -281,7 +285,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create values: one with actual value, one with empty string, one with NULL (no value), another with value
|
||||
{:ok, _cfv1} =
|
||||
|
|
@ -291,7 +295,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Zebra"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv2} =
|
||||
CustomFieldValue
|
||||
|
|
@ -300,7 +304,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => ""}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# member_with_null has no custom field value (NULL)
|
||||
|
||||
|
|
@ -311,7 +315,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Apple"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
|
||||
|
|
@ -347,6 +351,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
end
|
||||
|
||||
test "NULL values and empty strings are always sorted last (DESC)", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create additional members with NULL and empty string values
|
||||
{:ok, member_with_value} =
|
||||
Member
|
||||
|
|
@ -355,7 +361,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Test",
|
||||
email: "withvalue@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member_with_empty} =
|
||||
Member
|
||||
|
|
@ -364,7 +370,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Test",
|
||||
email: "withempty@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member_with_null} =
|
||||
Member
|
||||
|
|
@ -373,7 +379,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Test",
|
||||
email: "withnull@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member_with_another_value} =
|
||||
Member
|
||||
|
|
@ -382,7 +388,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
last_name: "Test",
|
||||
email: "another@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field
|
||||
{:ok, field} =
|
||||
|
|
@ -392,7 +398,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create values: one with actual value, one with empty string, one with NULL (no value), another with value
|
||||
{:ok, _cfv1} =
|
||||
|
|
@ -402,7 +408,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Apple"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv2} =
|
||||
CustomFieldValue
|
||||
|
|
@ -411,7 +417,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => ""}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# member_with_null has no custom field value (NULL)
|
||||
|
||||
|
|
@ -422,7 +428,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
|||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Zebra"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create test members
|
||||
{:ok, member1} =
|
||||
Member
|
||||
|
|
@ -29,7 +31,7 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
street: "Main St",
|
||||
city: "Berlin"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member2} =
|
||||
Member
|
||||
|
|
@ -40,7 +42,7 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
street: "Second St",
|
||||
city: "Hamburg"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field
|
||||
{:ok, custom_field} =
|
||||
|
|
@ -50,7 +52,7 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# Create custom field values
|
||||
{:ok, _cfv1} =
|
||||
|
|
@ -60,7 +62,7 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
custom_field_id: custom_field.id,
|
||||
value: "M001"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, _cfv2} =
|
||||
CustomFieldValue
|
||||
|
|
@ -69,7 +71,7 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
custom_field_id: custom_field.id,
|
||||
value: "M002"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
%{
|
||||
member1: member1,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ defmodule MvWeb.MemberLive.IndexMemberFieldsDisplayTest do
|
|||
alias Mv.Membership.Member
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
{:ok, member1} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
|
|
@ -18,7 +20,7 @@ defmodule MvWeb.MemberLive.IndexMemberFieldsDisplayTest do
|
|||
city: "Berlin",
|
||||
join_date: ~D[2020-01-15]
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
{:ok, member2} =
|
||||
Member
|
||||
|
|
@ -27,7 +29,7 @@ defmodule MvWeb.MemberLive.IndexMemberFieldsDisplayTest do
|
|||
last_name: "Brown",
|
||||
email: "bob@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
%{
|
||||
member1: member1,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do
|
|||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -24,11 +26,13 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do
|
|||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
# Helper to create a member
|
||||
defp create_member(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
|
|
@ -39,18 +43,20 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do
|
|||
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
# Helper to create a cycle
|
||||
defp create_cycle(member, fee_type, attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Delete any auto-generated cycles first to avoid conflicts
|
||||
existing_cycles =
|
||||
MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: system_actor)
|
||||
|
||||
Enum.each(existing_cycles, fn cycle -> Ash.destroy!(cycle) end)
|
||||
Enum.each(existing_cycles, fn cycle -> Ash.destroy!(cycle, actor: system_actor) end)
|
||||
|
||||
default_attrs = %{
|
||||
cycle_start: ~D[2023-01-01],
|
||||
|
|
@ -64,7 +70,7 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do
|
|||
|
||||
MembershipFeeCycle
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
describe "status column display" do
|
||||
|
|
@ -172,16 +178,18 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do
|
|||
member2 = create_member(%{first_name: "PaidMember", membership_fee_type_id: fee_type.id})
|
||||
create_cycle(member2, fee_type, %{cycle_start: ~D[2023-01-01], status: :paid})
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Verify cycles exist in database
|
||||
cycles1 =
|
||||
MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member1.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: system_actor)
|
||||
|
||||
cycles2 =
|
||||
MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member2.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: system_actor)
|
||||
|
||||
refute Enum.empty?(cycles1)
|
||||
refute Enum.empty?(cycles2)
|
||||
|
|
@ -206,16 +214,18 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do
|
|||
member2 = create_member(%{first_name: "PaidCurrent", membership_fee_type_id: fee_type.id})
|
||||
create_cycle(member2, fee_type, %{cycle_start: current_year_start, status: :paid})
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Verify cycles exist in database
|
||||
cycles1 =
|
||||
MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member1.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: system_actor)
|
||||
|
||||
cycles2 =
|
||||
MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member2.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: system_actor)
|
||||
|
||||
refute Enum.empty?(cycles1)
|
||||
refute Enum.empty?(cycles2)
|
||||
|
|
|
|||
|
|
@ -266,13 +266,18 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
end
|
||||
|
||||
test "can delete a member without error", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create a test member first
|
||||
{:ok, member} =
|
||||
Mv.Membership.create_member(%{
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
email: "test@example.com"
|
||||
})
|
||||
Mv.Membership.create_member(
|
||||
%{
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
email: "test@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, index_view, _html} = live(conn, "/members")
|
||||
|
|
@ -294,27 +299,38 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
|
||||
describe "copy_emails feature" do
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create test members
|
||||
{:ok, member1} =
|
||||
Mv.Membership.create_member(%{
|
||||
first_name: "Max",
|
||||
last_name: "Mustermann",
|
||||
email: "max@example.com"
|
||||
})
|
||||
Mv.Membership.create_member(
|
||||
%{
|
||||
first_name: "Max",
|
||||
last_name: "Mustermann",
|
||||
email: "max@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, member2} =
|
||||
Mv.Membership.create_member(%{
|
||||
first_name: "Erika",
|
||||
last_name: "Musterfrau",
|
||||
email: "erika@example.com"
|
||||
})
|
||||
Mv.Membership.create_member(
|
||||
%{
|
||||
first_name: "Erika",
|
||||
last_name: "Musterfrau",
|
||||
email: "erika@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, member3} =
|
||||
Mv.Membership.create_member(%{
|
||||
first_name: "Hans",
|
||||
last_name: "Müller-Lüdenscheidt",
|
||||
email: "hans@example.com"
|
||||
})
|
||||
Mv.Membership.create_member(
|
||||
%{
|
||||
first_name: "Hans",
|
||||
last_name: "Müller-Lüdenscheidt",
|
||||
email: "hans@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
%{member1: member1, member2: member2, member3: member3}
|
||||
end
|
||||
|
|
@ -394,7 +410,8 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
render_click(view, "select_member", %{"id" => member1.id})
|
||||
|
||||
# Delete the member from the database
|
||||
Ash.destroy!(member1)
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
Ash.destroy!(member1, actor: system_actor)
|
||||
|
||||
# Trigger copy_emails event directly - selection still contains the deleted ID
|
||||
# but the member is no longer in @members list after reload
|
||||
|
|
@ -434,12 +451,17 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
conn = conn_with_oidc_user(conn)
|
||||
|
||||
# Create a member with known data
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
{:ok, test_member} =
|
||||
Mv.Membership.create_member(%{
|
||||
first_name: "Test",
|
||||
last_name: "Format",
|
||||
email: "test.format@example.com"
|
||||
})
|
||||
Mv.Membership.create_member(
|
||||
%{
|
||||
first_name: "Test",
|
||||
last_name: "Format",
|
||||
email: "test.format@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
|
|
@ -500,8 +522,26 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
end
|
||||
|
||||
describe "cycle status filter" do
|
||||
alias Mv.MembershipFees.MembershipFeeType
|
||||
alias Mv.MembershipFees.MembershipFeeCycle
|
||||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs, actor) do
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
interval: :yearly
|
||||
}
|
||||
|
||||
attrs = Map.merge(default_attrs, attrs)
|
||||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
# Helper to create a member
|
||||
defp create_member(attrs) do
|
||||
defp create_member(attrs, actor) do
|
||||
default_attrs = %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
|
|
@ -512,32 +552,74 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
|
||||
Mv.Membership.Member
|
||||
|> Ash.Changeset.for_create(:create_member, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
# Helper to create a cycle
|
||||
defp create_cycle(member, fee_type, attrs, actor) do
|
||||
# Delete any auto-generated cycles first to avoid conflicts
|
||||
existing_cycles =
|
||||
MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member.id)
|
||||
|> Ash.read!(actor: actor)
|
||||
|
||||
Enum.each(existing_cycles, fn cycle -> Ash.destroy!(cycle, actor: actor) end)
|
||||
|
||||
default_attrs = %{
|
||||
cycle_start: ~D[2023-01-01],
|
||||
amount: Decimal.new("50.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id,
|
||||
status: :unpaid
|
||||
}
|
||||
|
||||
attrs = Map.merge(default_attrs, attrs)
|
||||
|
||||
MembershipFeeCycle
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
test "filter shows only members with paid status in last cycle", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = conn_with_oidc_user(conn)
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
fee_type = create_fee_type(%{interval: :yearly}, system_actor)
|
||||
today = Date.utc_today()
|
||||
last_year_start = Date.new!(today.year - 1, 1, 1)
|
||||
|
||||
# Member with paid last cycle
|
||||
paid_member =
|
||||
create_member(%{
|
||||
first_name: "PaidLast",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
create_member(
|
||||
%{
|
||||
first_name: "PaidLast",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
system_actor
|
||||
)
|
||||
|
||||
create_cycle(paid_member, fee_type, %{cycle_start: last_year_start, status: :paid})
|
||||
create_cycle(
|
||||
paid_member,
|
||||
fee_type,
|
||||
%{cycle_start: last_year_start, status: :paid},
|
||||
system_actor
|
||||
)
|
||||
|
||||
# Member with unpaid last cycle
|
||||
unpaid_member =
|
||||
create_member(%{
|
||||
first_name: "UnpaidLast",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
create_member(
|
||||
%{
|
||||
first_name: "UnpaidLast",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
system_actor
|
||||
)
|
||||
|
||||
create_cycle(unpaid_member, fee_type, %{cycle_start: last_year_start, status: :unpaid})
|
||||
create_cycle(
|
||||
unpaid_member,
|
||||
fee_type,
|
||||
%{cycle_start: last_year_start, status: :unpaid},
|
||||
system_actor
|
||||
)
|
||||
|
||||
{:ok, _view, html} = live(conn, "/members?cycle_status_filter=paid")
|
||||
|
||||
|
|
@ -546,28 +628,45 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
end
|
||||
|
||||
test "filter shows only members with unpaid status in last cycle", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = conn_with_oidc_user(conn)
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
fee_type = create_fee_type(%{interval: :yearly}, system_actor)
|
||||
today = Date.utc_today()
|
||||
last_year_start = Date.new!(today.year - 1, 1, 1)
|
||||
|
||||
# Member with paid last cycle
|
||||
paid_member =
|
||||
create_member(%{
|
||||
first_name: "PaidLast",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
create_member(
|
||||
%{
|
||||
first_name: "PaidLast",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
system_actor
|
||||
)
|
||||
|
||||
create_cycle(paid_member, fee_type, %{cycle_start: last_year_start, status: :paid})
|
||||
create_cycle(
|
||||
paid_member,
|
||||
fee_type,
|
||||
%{cycle_start: last_year_start, status: :paid},
|
||||
system_actor
|
||||
)
|
||||
|
||||
# Member with unpaid last cycle
|
||||
unpaid_member =
|
||||
create_member(%{
|
||||
first_name: "UnpaidLast",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
create_member(
|
||||
%{
|
||||
first_name: "UnpaidLast",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
system_actor
|
||||
)
|
||||
|
||||
create_cycle(unpaid_member, fee_type, %{cycle_start: last_year_start, status: :unpaid})
|
||||
create_cycle(
|
||||
unpaid_member,
|
||||
fee_type,
|
||||
%{cycle_start: last_year_start, status: :unpaid},
|
||||
system_actor
|
||||
)
|
||||
|
||||
{:ok, _view, html} = live(conn, "/members?cycle_status_filter=unpaid")
|
||||
|
||||
|
|
@ -576,28 +675,45 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
end
|
||||
|
||||
test "filter shows only members with paid status in current cycle", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = conn_with_oidc_user(conn)
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
fee_type = create_fee_type(%{interval: :yearly}, system_actor)
|
||||
today = Date.utc_today()
|
||||
current_year_start = Date.new!(today.year, 1, 1)
|
||||
|
||||
# Member with paid current cycle
|
||||
paid_member =
|
||||
create_member(%{
|
||||
first_name: "PaidCurrent",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
create_member(
|
||||
%{
|
||||
first_name: "PaidCurrent",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
system_actor
|
||||
)
|
||||
|
||||
create_cycle(paid_member, fee_type, %{cycle_start: current_year_start, status: :paid})
|
||||
create_cycle(
|
||||
paid_member,
|
||||
fee_type,
|
||||
%{cycle_start: current_year_start, status: :paid},
|
||||
system_actor
|
||||
)
|
||||
|
||||
# Member with unpaid current cycle
|
||||
unpaid_member =
|
||||
create_member(%{
|
||||
first_name: "UnpaidCurrent",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
create_member(
|
||||
%{
|
||||
first_name: "UnpaidCurrent",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
system_actor
|
||||
)
|
||||
|
||||
create_cycle(unpaid_member, fee_type, %{cycle_start: current_year_start, status: :unpaid})
|
||||
create_cycle(
|
||||
unpaid_member,
|
||||
fee_type,
|
||||
%{cycle_start: current_year_start, status: :unpaid},
|
||||
system_actor
|
||||
)
|
||||
|
||||
{:ok, _view, html} = live(conn, "/members?cycle_status_filter=paid&show_current_cycle=true")
|
||||
|
||||
|
|
@ -606,28 +722,45 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
end
|
||||
|
||||
test "filter shows only members with unpaid status in current cycle", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = conn_with_oidc_user(conn)
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
fee_type = create_fee_type(%{interval: :yearly}, system_actor)
|
||||
today = Date.utc_today()
|
||||
current_year_start = Date.new!(today.year, 1, 1)
|
||||
|
||||
# Member with paid current cycle
|
||||
paid_member =
|
||||
create_member(%{
|
||||
first_name: "PaidCurrent",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
create_member(
|
||||
%{
|
||||
first_name: "PaidCurrent",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
system_actor
|
||||
)
|
||||
|
||||
create_cycle(paid_member, fee_type, %{cycle_start: current_year_start, status: :paid})
|
||||
create_cycle(
|
||||
paid_member,
|
||||
fee_type,
|
||||
%{cycle_start: current_year_start, status: :paid},
|
||||
system_actor
|
||||
)
|
||||
|
||||
# Member with unpaid current cycle
|
||||
unpaid_member =
|
||||
create_member(%{
|
||||
first_name: "UnpaidCurrent",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
create_member(
|
||||
%{
|
||||
first_name: "UnpaidCurrent",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
system_actor
|
||||
)
|
||||
|
||||
create_cycle(unpaid_member, fee_type, %{cycle_start: current_year_start, status: :unpaid})
|
||||
create_cycle(
|
||||
unpaid_member,
|
||||
fee_type,
|
||||
%{cycle_start: current_year_start, status: :unpaid},
|
||||
system_actor
|
||||
)
|
||||
|
||||
{:ok, _view, html} =
|
||||
live(conn, "/members?cycle_status_filter=unpaid&show_current_cycle=true")
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -24,11 +26,13 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
# Helper to create a member
|
||||
defp create_member(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
|
|
@ -39,7 +43,7 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
describe "end-to-end workflows" do
|
||||
|
|
@ -75,7 +79,13 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
|> render_click()
|
||||
|
||||
# Verify status changed
|
||||
updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id))
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
updated_cycle =
|
||||
Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id),
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert updated_cycle.status == :paid
|
||||
end
|
||||
end
|
||||
|
|
@ -115,13 +125,14 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
|
||||
# Update settings
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{:ok, settings} = Mv.Membership.get_settings()
|
||||
|
||||
settings
|
||||
|> Ash.Changeset.for_update(:update_membership_fee_settings, %{
|
||||
default_membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: system_actor)
|
||||
|
||||
# Create new member
|
||||
{:ok, view, _html} = live(conn, "/members/new")
|
||||
|
|
@ -138,10 +149,12 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
|> render_submit()
|
||||
|
||||
# Verify member got default type
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
member =
|
||||
Member
|
||||
|> Ash.Query.filter(email == ^form_data["member[email]"])
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: system_actor)
|
||||
|
||||
assert member.membership_fee_type_id == fee_type.id
|
||||
end
|
||||
|
|
@ -150,6 +163,8 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
cycle =
|
||||
MembershipFeeCycle
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
|
|
@ -159,7 +174,7 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
membership_fee_type_id: fee_type.id,
|
||||
status: :unpaid
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
|
||||
{:ok, view, _html} = live(conn, "/members/#{member.id}")
|
||||
|
||||
|
|
@ -187,6 +202,8 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
cycle =
|
||||
MembershipFeeCycle
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
|
|
@ -196,7 +213,7 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
membership_fee_type_id: fee_type.id,
|
||||
status: :unpaid
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
|
||||
{:ok, view, _html} = live(conn, "/members/#{member.id}")
|
||||
|
||||
|
|
@ -216,7 +233,13 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|||
|> render_submit()
|
||||
|
||||
# Verify amount updated
|
||||
updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id))
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
updated_cycle =
|
||||
Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id),
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert updated_cycle.amount == Decimal.new("75.00")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
|
|||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -24,11 +26,13 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
|
|||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
# Helper to create a member
|
||||
defp create_member(attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
default_attrs = %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
|
|
@ -39,18 +43,20 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
|
|||
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
# Helper to create a cycle
|
||||
defp create_cycle(member, fee_type, attrs) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Delete any auto-generated cycles first to avoid conflicts
|
||||
existing_cycles =
|
||||
MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member.id)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: system_actor)
|
||||
|
||||
Enum.each(existing_cycles, fn cycle -> Ash.destroy!(cycle) end)
|
||||
Enum.each(existing_cycles, fn cycle -> Ash.destroy!(cycle, actor: system_actor) end)
|
||||
|
||||
default_attrs = %{
|
||||
cycle_start: ~D[2023-01-01],
|
||||
|
|
@ -64,7 +70,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
|
|||
|
||||
MembershipFeeCycle
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: system_actor)
|
||||
end
|
||||
|
||||
describe "cycles table display" do
|
||||
|
|
@ -161,7 +167,13 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
|
|||
|> render_click()
|
||||
|
||||
# Verify cycle is now paid
|
||||
updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id))
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
updated_cycle =
|
||||
Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id),
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert updated_cycle.status == :paid
|
||||
end
|
||||
|
||||
|
|
@ -186,7 +198,13 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
|
|||
|> render_click()
|
||||
|
||||
# Verify cycle is now suspended
|
||||
updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id))
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
updated_cycle =
|
||||
Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id),
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert updated_cycle.status == :suspended
|
||||
end
|
||||
|
||||
|
|
@ -211,7 +229,13 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
|
|||
|> render_click()
|
||||
|
||||
# Verify cycle is now unpaid
|
||||
updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id))
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
updated_cycle =
|
||||
Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id),
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert updated_cycle.status == :unpaid
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create test member
|
||||
{:ok, member} =
|
||||
Member
|
||||
|
|
@ -29,15 +31,16 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
%{member: member}
|
||||
%{member: member, actor: system_actor}
|
||||
end
|
||||
|
||||
describe "custom fields section visibility (Issue #282)" do
|
||||
test "displays Custom Fields section even when member has no custom field values", %{
|
||||
conn: conn,
|
||||
member: member
|
||||
member: member,
|
||||
actor: actor
|
||||
} do
|
||||
# Create a custom field but no value for the member
|
||||
{:ok, custom_field} =
|
||||
|
|
@ -46,7 +49,7 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
name: "phone_mobile",
|
||||
value_type: :string
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||
|
|
@ -63,7 +66,8 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
|
||||
test "displays Custom Fields section with multiple custom fields, some without values", %{
|
||||
conn: conn,
|
||||
member: member
|
||||
member: member,
|
||||
actor: actor
|
||||
} do
|
||||
# Create multiple custom fields
|
||||
{:ok, field1} =
|
||||
|
|
@ -72,7 +76,7 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
name: "phone_mobile",
|
||||
value_type: :string
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
{:ok, field2} =
|
||||
CustomField
|
||||
|
|
@ -80,7 +84,7 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
name: "membership_number",
|
||||
value_type: :integer
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
# Create value only for first field
|
||||
{:ok, _cfv} =
|
||||
|
|
@ -90,7 +94,7 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
custom_field_id: field1.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "+49123456789"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||
|
|
@ -111,18 +115,19 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
|
||||
test "does not display Custom Fields section when no custom fields exist", %{
|
||||
conn: conn,
|
||||
member: member
|
||||
member: member,
|
||||
actor: actor
|
||||
} do
|
||||
# Ensure no custom fields exist for this test
|
||||
# This ensures test isolation even if previous tests created custom fields
|
||||
existing_custom_fields = Ash.read!(CustomField)
|
||||
existing_custom_fields = Ash.read!(CustomField, actor: actor)
|
||||
|
||||
for cf <- existing_custom_fields do
|
||||
Ash.destroy!(cf)
|
||||
Ash.destroy!(cf, actor: actor)
|
||||
end
|
||||
|
||||
# Verify no custom fields exist
|
||||
assert Ash.read!(CustomField) == []
|
||||
assert Ash.read!(CustomField, actor: actor) == []
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||
|
|
@ -133,14 +138,14 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
end
|
||||
|
||||
describe "custom field value formatting" do
|
||||
test "formats string custom field values", %{conn: conn, member: member} do
|
||||
test "formats string custom field values", %{conn: conn, member: member, actor: actor} do
|
||||
{:ok, custom_field} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "phone_mobile",
|
||||
value_type: :string
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
{:ok, _cfv} =
|
||||
CustomFieldValue
|
||||
|
|
@ -149,7 +154,7 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
custom_field_id: custom_field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "+49123456789"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||
|
|
@ -157,14 +162,18 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
assert html =~ "+49123456789"
|
||||
end
|
||||
|
||||
test "formats email custom field values as mailto links", %{conn: conn, member: member} do
|
||||
test "formats email custom field values as mailto links", %{
|
||||
conn: conn,
|
||||
member: member,
|
||||
actor: actor
|
||||
} do
|
||||
{:ok, custom_field} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "private_email",
|
||||
value_type: :email
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
{:ok, _cfv} =
|
||||
CustomFieldValue
|
||||
|
|
@ -173,7 +182,7 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
custom_field_id: custom_field.id,
|
||||
value: %{"_union_type" => "email", "_union_value" => "private@example.com"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||
|
|
|
|||
|
|
@ -70,12 +70,17 @@ defmodule MvWeb.UserLive.FormMemberDropdownTest do
|
|||
test "links user and member with identical email successfully", %{conn: conn} do
|
||||
conn = setup_admin_conn(conn)
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "David",
|
||||
last_name: "Miller",
|
||||
email: "david@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "David",
|
||||
last_name: "Miller",
|
||||
email: "david@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/new")
|
||||
|
||||
|
|
@ -106,12 +111,17 @@ defmodule MvWeb.UserLive.FormMemberDropdownTest do
|
|||
test "shows member with same email in dropdown", %{conn: conn} do
|
||||
conn = setup_admin_conn(conn)
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
{:ok, _member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Emma",
|
||||
last_name: "Davis",
|
||||
email: "emma@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Emma",
|
||||
last_name: "Davis",
|
||||
email: "emma@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/new")
|
||||
|
||||
|
|
@ -135,13 +145,18 @@ defmodule MvWeb.UserLive.FormMemberDropdownTest do
|
|||
|
||||
# Helper functions
|
||||
defp create_unlinked_members(count) do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
for i <- 1..count do
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "FirstName#{i}",
|
||||
last_name: "LastName#{i}",
|
||||
email: "member#{i}@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "FirstName#{i}",
|
||||
last_name: "LastName#{i}",
|
||||
email: "member#{i}@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
member
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,14 +18,18 @@ defmodule MvWeb.UserLive.FormMemberSearchTest do
|
|||
|
||||
describe "fuzzy search" do
|
||||
test "finds member with exact name", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
|
||||
{:ok, _member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Jonathan",
|
||||
last_name: "Smith",
|
||||
email: "jonathan.smith@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Jonathan",
|
||||
last_name: "Smith",
|
||||
email: "jonathan.smith@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/new")
|
||||
|
||||
|
|
@ -41,14 +45,18 @@ defmodule MvWeb.UserLive.FormMemberSearchTest do
|
|||
end
|
||||
|
||||
test "finds member with typo (Jon finds Jonathan)", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
|
||||
{:ok, _member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Jonathan",
|
||||
last_name: "Smith",
|
||||
email: "jonathan.smith@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Jonathan",
|
||||
last_name: "Smith",
|
||||
email: "jonathan.smith@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/new")
|
||||
|
||||
|
|
@ -65,14 +73,18 @@ defmodule MvWeb.UserLive.FormMemberSearchTest do
|
|||
end
|
||||
|
||||
test "finds member with partial substring", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
|
||||
{:ok, _member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Alexander",
|
||||
last_name: "Williams",
|
||||
email: "alex@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Alexander",
|
||||
last_name: "Williams",
|
||||
email: "alex@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/new")
|
||||
|
||||
|
|
@ -87,14 +99,18 @@ defmodule MvWeb.UserLive.FormMemberSearchTest do
|
|||
end
|
||||
|
||||
test "shows partial match with similar names", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
|
||||
{:ok, _member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Johnny",
|
||||
last_name: "Doeson",
|
||||
email: "johnny@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Johnny",
|
||||
last_name: "Doeson",
|
||||
email: "johnny@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/new")
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,18 @@ defmodule MvWeb.UserLive.FormMemberSelectionTest do
|
|||
|
||||
describe "member selection" do
|
||||
test "input field shows selected member name", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Alice",
|
||||
last_name: "Johnson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Alice",
|
||||
last_name: "Johnson",
|
||||
email: "alice@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/new")
|
||||
|
||||
|
|
@ -47,14 +51,18 @@ defmodule MvWeb.UserLive.FormMemberSelectionTest do
|
|||
end
|
||||
|
||||
test "confirmation box appears", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Bob",
|
||||
last_name: "Williams",
|
||||
email: "bob@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Bob",
|
||||
last_name: "Williams",
|
||||
email: "bob@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/new")
|
||||
|
||||
|
|
@ -77,14 +85,18 @@ defmodule MvWeb.UserLive.FormMemberSelectionTest do
|
|||
end
|
||||
|
||||
test "hidden input stores member ID", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Charlie",
|
||||
last_name: "Brown",
|
||||
email: "charlie@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Charlie",
|
||||
last_name: "Brown",
|
||||
email: "charlie@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/new")
|
||||
|
||||
|
|
@ -105,20 +117,27 @@ defmodule MvWeb.UserLive.FormMemberSelectionTest do
|
|||
|
||||
describe "unlink workflow" do
|
||||
test "unlink hides dropdown", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
# Create user with linked member
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Frank",
|
||||
last_name: "Wilson",
|
||||
email: "frank@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Frank",
|
||||
last_name: "Wilson",
|
||||
email: "frank@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, user} =
|
||||
Accounts.create_user(%{
|
||||
email: "frank@example.com",
|
||||
member: %{id: member.id}
|
||||
})
|
||||
Accounts.create_user(
|
||||
%{
|
||||
email: "frank@example.com",
|
||||
member: %{id: member.id}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/#{user.id}/edit")
|
||||
|
||||
|
|
@ -134,20 +153,27 @@ defmodule MvWeb.UserLive.FormMemberSelectionTest do
|
|||
end
|
||||
|
||||
test "unlink shows warning", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
# Create user with linked member
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Grace",
|
||||
last_name: "Taylor",
|
||||
email: "grace@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Grace",
|
||||
last_name: "Taylor",
|
||||
email: "grace@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, user} =
|
||||
Accounts.create_user(%{
|
||||
email: "grace@example.com",
|
||||
member: %{id: member.id}
|
||||
})
|
||||
Accounts.create_user(
|
||||
%{
|
||||
email: "grace@example.com",
|
||||
member: %{id: member.id}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/#{user.id}/edit")
|
||||
|
||||
|
|
@ -164,20 +190,27 @@ defmodule MvWeb.UserLive.FormMemberSelectionTest do
|
|||
end
|
||||
|
||||
test "unlink disables input", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
# Create user with linked member
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Henry",
|
||||
last_name: "Anderson",
|
||||
email: "henry@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Henry",
|
||||
last_name: "Anderson",
|
||||
email: "henry@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, user} =
|
||||
Accounts.create_user(%{
|
||||
email: "henry@example.com",
|
||||
member: %{id: member.id}
|
||||
})
|
||||
Accounts.create_user(
|
||||
%{
|
||||
email: "henry@example.com",
|
||||
member: %{id: member.id}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/#{user.id}/edit")
|
||||
|
||||
|
|
@ -193,20 +226,27 @@ defmodule MvWeb.UserLive.FormMemberSelectionTest do
|
|||
end
|
||||
|
||||
test "save re-enables member selection", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
conn = setup_admin_conn(conn)
|
||||
# Create user with linked member
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Isabel",
|
||||
last_name: "Martinez",
|
||||
email: "isabel@example.com"
|
||||
})
|
||||
Membership.create_member(
|
||||
%{
|
||||
first_name: "Isabel",
|
||||
last_name: "Martinez",
|
||||
email: "isabel@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, user} =
|
||||
Accounts.create_user(%{
|
||||
email: "isabel@example.com",
|
||||
member: %{id: member.id}
|
||||
})
|
||||
Accounts.create_user(
|
||||
%{
|
||||
email: "isabel@example.com",
|
||||
member: %{id: member.id}
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/users/#{user.id}/edit")
|
||||
|
||||
|
|
|
|||
|
|
@ -75,11 +75,14 @@ defmodule MvWeb.UserLive.FormTest do
|
|||
|> form("#user-form", user: %{email: "storetest@example.com"})
|
||||
|> render_submit()
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
user =
|
||||
Ash.get!(
|
||||
Mv.Accounts.User,
|
||||
[email: Ash.CiString.new("storetest@example.com")],
|
||||
domain: Mv.Accounts
|
||||
domain: Mv.Accounts,
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert to_string(user.email) == "storetest@example.com"
|
||||
|
|
@ -101,11 +104,14 @@ defmodule MvWeb.UserLive.FormTest do
|
|||
)
|
||||
|> render_submit()
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
user =
|
||||
Ash.get!(
|
||||
Mv.Accounts.User,
|
||||
[email: Ash.CiString.new("passwordstoretest@example.com")],
|
||||
domain: Mv.Accounts
|
||||
domain: Mv.Accounts,
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
assert user.hashed_password != nil
|
||||
|
|
@ -181,7 +187,8 @@ defmodule MvWeb.UserLive.FormTest do
|
|||
|
||||
assert_redirected(view, "/users")
|
||||
|
||||
updated_user = Ash.reload!(user, domain: Mv.Accounts)
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
updated_user = Ash.reload!(user, domain: Mv.Accounts, actor: system_actor)
|
||||
assert to_string(updated_user.email) == "new@example.com"
|
||||
assert updated_user.hashed_password == original_password
|
||||
end
|
||||
|
|
@ -204,7 +211,8 @@ defmodule MvWeb.UserLive.FormTest do
|
|||
|
||||
assert_redirected(view, "/users")
|
||||
|
||||
updated_user = Ash.reload!(user, domain: Mv.Accounts)
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
updated_user = Ash.reload!(user, domain: Mv.Accounts, actor: system_actor)
|
||||
assert updated_user.hashed_password != original_password
|
||||
assert String.starts_with?(updated_user.hashed_password, "$2b$")
|
||||
end
|
||||
|
|
@ -285,17 +293,24 @@ defmodule MvWeb.UserLive.FormTest do
|
|||
|
||||
describe "member linking - display" do
|
||||
test "shows linked member with unlink button when user has member", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create member
|
||||
{:ok, member} =
|
||||
Mv.Membership.create_member(%{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email: "john@example.com"
|
||||
})
|
||||
Mv.Membership.create_member(
|
||||
%{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email: "john@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
# Create user linked to member
|
||||
user = create_test_user(%{email: "user@example.com"})
|
||||
{:ok, _updated_user} = Mv.Accounts.update_user(user, %{member: %{id: member.id}})
|
||||
|
||||
{:ok, _updated_user} =
|
||||
Mv.Accounts.update_user(user, %{member: %{id: member.id}}, actor: system_actor)
|
||||
|
||||
# Load form
|
||||
{:ok, view, html} = setup_live_view(conn, "/users/#{user.id}/edit")
|
||||
|
|
@ -322,13 +337,18 @@ defmodule MvWeb.UserLive.FormTest do
|
|||
|
||||
describe "member linking - workflow" do
|
||||
test "selecting member and saving links member to user", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create unlinked member
|
||||
{:ok, member} =
|
||||
Mv.Membership.create_member(%{
|
||||
first_name: "Jane",
|
||||
last_name: "Smith",
|
||||
email: "jane@example.com"
|
||||
})
|
||||
Mv.Membership.create_member(
|
||||
%{
|
||||
first_name: "Jane",
|
||||
last_name: "Smith",
|
||||
email: "jane@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
# Create user without member
|
||||
user = create_test_user(%{email: "user@example.com"})
|
||||
|
|
@ -345,22 +365,35 @@ defmodule MvWeb.UserLive.FormTest do
|
|||
assert_redirected(view, "/users")
|
||||
|
||||
# Verify member is linked
|
||||
updated_user = Ash.get!(Mv.Accounts.User, user.id, domain: Mv.Accounts, load: [:member])
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
updated_user =
|
||||
Ash.get!(Mv.Accounts.User, user.id,
|
||||
domain: Mv.Accounts,
|
||||
actor: system_actor,
|
||||
load: [:member]
|
||||
)
|
||||
|
||||
assert updated_user.member.id == member.id
|
||||
end
|
||||
|
||||
test "unlinking member and saving removes member from user", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create member
|
||||
{:ok, member} =
|
||||
Mv.Membership.create_member(%{
|
||||
first_name: "Bob",
|
||||
last_name: "Wilson",
|
||||
email: "bob@example.com"
|
||||
})
|
||||
Mv.Membership.create_member(
|
||||
%{
|
||||
first_name: "Bob",
|
||||
last_name: "Wilson",
|
||||
email: "bob@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
# Create user linked to member
|
||||
user = create_test_user(%{email: "user@example.com"})
|
||||
{:ok, _} = Mv.Accounts.update_user(user, %{member: %{id: member.id}})
|
||||
{:ok, _} = Mv.Accounts.update_user(user, %{member: %{id: member.id}}, actor: system_actor)
|
||||
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/#{user.id}/edit")
|
||||
|
||||
|
|
@ -375,7 +408,15 @@ defmodule MvWeb.UserLive.FormTest do
|
|||
assert_redirected(view, "/users")
|
||||
|
||||
# Verify member is unlinked
|
||||
updated_user = Ash.get!(Mv.Accounts.User, user.id, domain: Mv.Accounts, load: [:member])
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
updated_user =
|
||||
Ash.get!(Mv.Accounts.User, user.id,
|
||||
domain: Mv.Accounts,
|
||||
actor: system_actor,
|
||||
load: [:member]
|
||||
)
|
||||
|
||||
assert is_nil(updated_user.member)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -407,17 +407,24 @@ defmodule MvWeb.UserLive.IndexTest do
|
|||
|
||||
describe "member linking display" do
|
||||
test "displays linked member name in user list", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create member
|
||||
{:ok, member} =
|
||||
Mv.Membership.create_member(%{
|
||||
first_name: "Alice",
|
||||
last_name: "Johnson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
Mv.Membership.create_member(
|
||||
%{
|
||||
first_name: "Alice",
|
||||
last_name: "Johnson",
|
||||
email: "alice@example.com"
|
||||
},
|
||||
actor: system_actor
|
||||
)
|
||||
|
||||
# Create user linked to member
|
||||
user = create_test_user(%{email: "user@example.com"})
|
||||
{:ok, _updated_user} = Mv.Accounts.update_user(user, %{member: %{id: member.id}})
|
||||
|
||||
{:ok, _updated_user} =
|
||||
Mv.Accounts.update_user(user, %{member: %{id: member.id}}, actor: system_actor)
|
||||
|
||||
# Create another user without member
|
||||
_unlinked_user = create_test_user(%{email: "unlinked@example.com"})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue