Merge branch 'main' into feature/filter-boolean-custom-fields
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Simon 2026-01-20 18:13:20 +01:00
commit 01dea8bb8b
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
41 changed files with 1886 additions and 6203 deletions

View file

@ -158,10 +158,8 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
|> Ash.update!()
# Create a member without explicitly setting membership_fee_type_id
# Note: This test assumes that the Member resource automatically assigns
# the default_membership_fee_type_id during creation. If this is not yet
# implemented, this test will fail initially (which is expected in TDD).
# For now, we skip this test as the auto-assignment feature is not yet implemented.
# The Member resource automatically assigns the default_membership_fee_type_id
# during creation via SetDefaultMembershipFeeType change.
{:ok, member} =
Ash.create(Member, %{
first_name: "Test",
@ -169,10 +167,8 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
email: "test.member.#{System.unique_integer([:positive])}@example.com"
})
# TODO: When auto-assignment is implemented, uncomment this assertion
# assert member.membership_fee_type_id == fee_type.id
# For now, we just verify the member was created successfully
assert %Member{} = member
# Verify that the default membership fee type was automatically assigned
assert member.membership_fee_type_id == fee_type.id
end
test "include_joining_cycle is used during cycle generation" do

View file

@ -404,7 +404,7 @@ defmodule Mv.Membership.Import.MemberCSVTest do
assert chunk_result.inserted == 0
assert chunk_result.failed == 10
assert length(chunk_result.errors) == 0
assert chunk_result.errors == []
end
test "error capping with mixed success and failure" do

View file

@ -146,8 +146,6 @@ defmodule MvWeb.ProfileNavigationTest do
"/",
"/members",
"/members/new",
"/custom_field_values",
"/custom_field_values/new",
"/users",
"/users/new"
]

View file

@ -0,0 +1,274 @@
defmodule MvWeb.RoleLive.ShowTest do
@moduledoc """
Tests for the role show page.
Tests cover:
- Displaying role information
- System role badge display
- User count display
- Navigation
- Error handling
- Delete functionality
"""
use MvWeb.ConnCase, async: false
import Phoenix.LiveViewTest
require Ash.Query
use Gettext, backend: MvWeb.Gettext
alias Mv.Authorization
alias Mv.Authorization.Role
# Helper to create a role
defp create_role(attrs \\ %{}) do
default_attrs = %{
name: "Test Role #{System.unique_integer([:positive])}",
description: "Test description",
permission_set_name: "read_only"
}
attrs = Map.merge(default_attrs, attrs)
case Authorization.create_role(attrs) do
{:ok, role} -> role
{:error, error} -> raise "Failed to create role: #{inspect(error)}"
end
end
# Helper to create admin user with admin role
defp create_admin_user(conn) do
# Create admin role
admin_role =
case Authorization.list_roles() do
{:ok, roles} ->
case Enum.find(roles, &(&1.name == "Admin")) do
nil ->
# Create admin role if it doesn't exist
create_role(%{
name: "Admin",
description: "Administrator with full access",
permission_set_name: "admin"
})
role ->
role
end
_ ->
# Create admin role if list_roles fails
create_role(%{
name: "Admin",
description: "Administrator with full access",
permission_set_name: "admin"
})
end
# Create user
{:ok, user} =
Mv.Accounts.User
|> Ash.Changeset.for_create(:register_with_password, %{
email: "admin#{System.unique_integer([:positive])}@mv.local",
password: "testpassword123"
})
|> Ash.create()
# 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()
# Load role for authorization checks (must be loaded for can?/3 to work)
user_with_role = Ash.load!(user, :role, domain: Mv.Accounts)
# Store user with role in session for LiveView
conn = conn_with_password_user(conn, user_with_role)
{conn, user_with_role, admin_role}
end
describe "mount and display" do
setup %{conn: conn} do
{conn, _user, _admin_role} = create_admin_user(conn)
%{conn: conn}
end
test "mounts successfully with valid role ID", %{conn: conn} do
role = create_role()
{:ok, _view, html} = live(conn, "/admin/roles/#{role.id}")
assert html =~ role.name
end
test "displays role name", %{conn: conn} do
role = create_role(%{name: "Test Role Name"})
{:ok, _view, html} = live(conn, "/admin/roles/#{role.id}")
assert html =~ "Test Role Name"
assert html =~ gettext("Name")
end
test "displays role description when present", %{conn: conn} do
role = create_role(%{description: "This is a test description"})
{:ok, _view, html} = live(conn, "/admin/roles/#{role.id}")
assert html =~ "This is a test description"
assert html =~ gettext("Description")
end
test "displays 'No description' when description is missing", %{conn: conn} do
role = create_role(%{description: nil})
{:ok, _view, html} = live(conn, "/admin/roles/#{role.id}")
assert html =~ gettext("No description")
end
test "displays permission set name", %{conn: conn} do
role = create_role(%{permission_set_name: "read_only"})
{:ok, _view, html} = live(conn, "/admin/roles/#{role.id}")
assert html =~ "read_only"
assert html =~ gettext("Permission Set")
end
test "displays system role badge when is_system_role is true", %{conn: conn} do
system_role =
Role
|> Ash.Changeset.for_create(:create_role, %{
name: "System Role",
permission_set_name: "own_data"
})
|> Ash.Changeset.force_change_attribute(:is_system_role, true)
|> Ash.create!()
{:ok, _view, html} = live(conn, "/admin/roles/#{system_role.id}")
assert html =~ gettext("System Role")
assert html =~ gettext("Yes")
end
test "displays non-system role badge when is_system_role is false", %{conn: conn} do
role = create_role()
{:ok, _view, html} = live(conn, "/admin/roles/#{role.id}")
assert html =~ gettext("System Role")
assert html =~ gettext("No")
end
test "displays user count", %{conn: conn} do
role = create_role()
{:ok, _view, html} = live(conn, "/admin/roles/#{role.id}")
# User count should be displayed (might be 0 or more)
assert html =~ gettext("User") || html =~ "0" || html =~ "users"
end
end
describe "navigation" do
setup %{conn: conn} do
{conn, _user, _admin_role} = create_admin_user(conn)
%{conn: conn}
end
test "back button navigates to role list", %{conn: conn} do
role = create_role()
{:ok, view, _html} = live(conn, "/admin/roles/#{role.id}")
assert {:error, {:live_redirect, %{to: to}}} =
view
|> element(
"a[aria-label='#{gettext("Back to roles list")}'], button[aria-label='#{gettext("Back to roles list")}']"
)
|> render_click()
assert to == "/admin/roles"
end
test "edit button navigates to edit form", %{conn: conn} do
role = create_role()
{:ok, view, _html} = live(conn, "/admin/roles/#{role.id}")
assert {:error, {:live_redirect, %{to: to}}} =
view
|> element(
"a[href='/admin/roles/#{role.id}/edit'], button[href='/admin/roles/#{role.id}/edit']"
)
|> render_click()
assert to == "/admin/roles/#{role.id}/edit"
end
end
describe "error handling" do
setup %{conn: conn} do
{conn, _user, _admin_role} = create_admin_user(conn)
%{conn: conn}
end
test "redirects to role list with error for invalid role ID", %{conn: conn} do
invalid_id = Ecto.UUID.generate()
# Should redirect to index with error message
result = live(conn, "/admin/roles/#{invalid_id}")
assert match?({:error, {:redirect, %{to: "/admin/roles"}}}, result) or
match?({:error, {:live_redirect, %{to: "/admin/roles"}}}, result)
end
end
describe "delete functionality" do
setup %{conn: conn} do
{conn, _user, _admin_role} = create_admin_user(conn)
%{conn: conn}
end
test "delete button is not shown for system roles", %{conn: conn} do
system_role =
Role
|> Ash.Changeset.for_create(:create_role, %{
name: "System Role",
permission_set_name: "own_data"
})
|> Ash.Changeset.force_change_attribute(:is_system_role, true)
|> Ash.create!()
{:ok, _view, html} = live(conn, "/admin/roles/#{system_role.id}")
# Delete button should not be visible for system roles
refute html =~ ~r/Delete.*Role.*#{system_role.id}/i
end
test "delete button is shown for non-system roles", %{conn: conn} do
role = create_role()
{:ok, _view, html} = live(conn, "/admin/roles/#{role.id}")
# Delete button should be visible for non-system roles
assert html =~ gettext("Delete Role") || html =~ "delete"
end
end
describe "page title" do
setup %{conn: conn} do
{conn, _user, _admin_role} = create_admin_user(conn)
%{conn: conn}
end
test "sets correct page title", %{conn: conn} do
role = create_role()
{:ok, _view, html} = live(conn, "/admin/roles/#{role.id}")
# Check that page title is set (might be in title tag or header)
assert html =~ gettext("Show Role") || html =~ role.name
end
end
end

View file

@ -0,0 +1,155 @@
defmodule MvWeb.UserLive.ShowTest do
@moduledoc """
Tests for the user show page.
Tests cover:
- Displaying user information
- Authentication status display
- Linked member display
- Navigation
- Error handling
"""
use MvWeb.ConnCase, async: true
import Phoenix.LiveViewTest
require Ash.Query
use Gettext, backend: MvWeb.Gettext
alias Mv.Membership.Member
setup do
# Create test user
user = create_test_user(%{email: "test@example.com", oidc_id: "test123"})
%{user: user}
end
describe "mount and display" do
test "mounts successfully with valid user ID", %{conn: conn, user: user} do
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, ~p"/users/#{user.id}")
assert html =~ to_string(user.email)
end
test "displays user email", %{conn: conn, user: user} do
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, ~p"/users/#{user.id}")
assert html =~ to_string(user.email)
assert html =~ gettext("Email")
end
test "displays password authentication status when enabled", %{conn: conn} do
user = create_test_user(%{email: "password-user@example.com", password: "test123"})
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, ~p"/users/#{user.id}")
assert html =~ gettext("Password Authentication")
assert html =~ gettext("Enabled")
end
test "displays password authentication status when not enabled", %{conn: conn} do
# User without password (only OIDC) - create user with OIDC only
user =
create_test_user(%{
email: "oidc-only#{System.unique_integer([:positive])}@example.com",
oidc_id: "oidc#{System.unique_integer([:positive])}",
hashed_password: nil
})
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, ~p"/users/#{user.id}")
assert html =~ gettext("Password Authentication")
assert html =~ gettext("Not enabled")
end
test "displays linked member when present", %{conn: conn} do
# Create member
{:ok, member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Alice",
last_name: "Smith",
email: "alice@example.com"
})
|> Ash.create()
# Create user and link to member
user = create_test_user(%{email: "user@example.com"})
{:ok, _updated_user} =
user
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:member, member, type: :append_and_remove)
|> Ash.update()
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, ~p"/users/#{user.id}")
assert html =~ gettext("Linked Member")
assert html =~ "Alice Smith"
assert html =~ ~r/href="[^"]*\/members\/#{member.id}"/
end
test "displays 'No member linked' when no member is linked", %{conn: conn, user: user} do
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, ~p"/users/#{user.id}")
assert html =~ gettext("Linked Member")
assert html =~ gettext("No member linked")
end
end
describe "navigation" do
test "back button navigates to user list", %{conn: conn, user: user} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, ~p"/users/#{user.id}")
assert {:error, {:live_redirect, %{to: to}}} =
view
|> element(
"a[aria-label='#{gettext("Back to users list")}'], button[aria-label='#{gettext("Back to users list")}']"
)
|> render_click()
assert to == "/users"
end
test "edit button navigates to edit form", %{conn: conn, user: user} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, ~p"/users/#{user.id}")
assert {:error, {:live_redirect, %{to: to}}} =
view
|> element(
"a[href='/users/#{user.id}/edit?return_to=show'], button[href='/users/#{user.id}/edit?return_to=show']"
)
|> render_click()
assert to == "/users/#{user.id}/edit?return_to=show"
end
end
describe "error handling" do
test "raises exception for invalid user ID", %{conn: conn} do
invalid_id = Ecto.UUID.generate()
conn = conn_with_oidc_user(conn)
# The mount function uses Ash.get! which will raise an exception
# This is expected behavior - the LiveView doesn't handle this case
assert_raise Ash.Error.Invalid, fn ->
live(conn, ~p"/users/#{invalid_id}")
end
end
end
describe "page title" do
test "sets correct page title", %{conn: conn, user: user} do
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, ~p"/users/#{user.id}")
# Check that page title is set (might be in title tag or header)
assert html =~ gettext("Show User") || html =~ to_string(user.email)
end
end
end