mitgliederverwaltung/test/mv_web/live/group_live/show_test.exs
Simon f05fae3ea3
Some checks failed
continuous-integration/drone/push Build is failing
test: add tdd tests for groups administration #372
2026-01-27 18:24:42 +01:00

281 lines
9 KiB
Elixir

defmodule MvWeb.GroupLive.ShowTest do
@moduledoc """
Tests for the group detail page.
Tests cover:
- Displaying group information
- Slug-based routing
- Member list display
- Navigation
- Error handling
- Delete functionality
"""
use MvWeb.ConnCase, async: false
import Phoenix.LiveViewTest
require Ash.Query
use Gettext, backend: MvWeb.Gettext
alias Mv.Membership
alias Mv.Fixtures
describe "mount and display" do
test "page renders successfully", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ group.name
end
test "displays group name", %{conn: conn} do
group = Fixtures.group_fixture(%{name: "Test Group Name"})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ "Test Group Name"
end
test "displays group description when present", %{conn: conn} do
group = Fixtures.group_fixture(%{description: "This is a test description"})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ "This is a test description"
end
test "displays member count", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
# Member count should be displayed (might be 0 or more)
assert html =~ "0" || html =~ gettext("Members") || html =~ "member"
end
test "displays list of members in group", %{conn: conn} do
group = Fixtures.group_fixture()
member1 = Fixtures.member_fixture(%{first_name: "Alice", last_name: "Smith"})
member2 = Fixtures.member_fixture(%{first_name: "Bob", last_name: "Jones"})
system_actor = Mv.Helpers.SystemActor.get_system_actor()
Membership.create_member_group(%{member_id: member1.id, group_id: group.id},
actor: system_actor
)
Membership.create_member_group(%{member_id: member2.id, group_id: group.id},
actor: system_actor
)
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ "Alice" || html =~ "Smith"
assert html =~ "Bob" || html =~ "Jones"
end
test "displays edit button for admin users", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ gettext("Edit") || html =~ "edit"
end
test "displays delete button for admin users", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ gettext("Delete") || html =~ "delete"
end
end
describe "slug-based routing" do
test "route /groups/:slug works correctly", %{conn: conn} do
group = Fixtures.group_fixture(%{name: "Board Members"})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ "Board Members"
# Verify slug is in URL
assert html =~ group.slug || html =~ "board-members"
end
test "group is found by slug via unique_slug identity", %{conn: conn} do
group = Fixtures.group_fixture(%{name: "Test Group"})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ group.name
end
test "non-existent slug returns 404", %{conn: conn} do
non_existent_slug = "non-existent-group-slug"
result = live(conn, "/groups/#{non_existent_slug}")
assert match?({:error, {:redirect, %{to: "/groups"}}}, result) ||
match?({:error, {:live_redirect, %{to: "/groups"}}}, result)
end
test "slug is case-sensitive (exact match required)", %{conn: conn} do
group = Fixtures.group_fixture(%{name: "Test Group"})
# Slug should be lowercase
wrong_case_slug = String.upcase(group.slug)
result = live(conn, "/groups/#{wrong_case_slug}")
# Should not find group with wrong case
assert match?({:error, {:redirect, %{to: "/groups"}}}, result) ||
match?({:error, {:live_redirect, %{to: "/groups"}}}, result)
end
test "URL is readable and user-friendly", %{conn: conn} do
group = Fixtures.group_fixture(%{name: "Board Members"})
{:ok, _view, _html} = live(conn, "/groups/#{group.slug}")
# URL should contain readable slug, not UUID
assert group.slug == "board-members"
refute String.contains?(group.slug, "-") == false
end
end
describe "edge cases" do
test "displays empty group correctly (0 members)", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ "0" || html =~ gettext("No members") || html =~ "empty"
end
test "handles group without description correctly", %{conn: conn} do
group = Fixtures.group_fixture(%{description: nil})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
# Should not crash, description should be optional
assert html =~ group.name
end
test "handles slug with special characters correctly", %{conn: conn} do
# Create group with name that generates slug with hyphens
group = Fixtures.group_fixture(%{name: "Test-Group-Name"})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ "Test-Group-Name" || html =~ group.name
end
end
describe "security" do
@tag role: :member
test "read-only users can view group detail page", %{conn: conn} do
group = Fixtures.group_fixture()
read_only_user = Fixtures.user_with_role_fixture("read_only")
conn = conn_with_password_user(conn, read_only_user)
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ group.name
# Should NOT see edit/delete buttons
refute html =~ gettext("Edit") || html =~ gettext("Delete")
end
@tag role: :unauthenticated
test "unauthenticated users are redirected to login", %{conn: conn} do
group = Fixtures.group_fixture()
result = live(conn, "/groups/#{group.slug}")
assert match?({:error, {:redirect, %{to: "/auth/sign_in"}}}, result) ||
match?({:error, {:live_redirect, %{to: "/auth/sign_in"}}}, result)
end
test "slug injection attempts are prevented", %{conn: conn} do
# Try to inject SQL or other malicious content in slug
malicious_slug = "'; DROP TABLE groups; --"
result = live(conn, "/groups/#{malicious_slug}")
# Should not execute SQL, should return 404 or error
assert match?({:error, {:redirect, %{to: "/groups"}}}, result) ||
match?({:error, {:live_redirect, %{to: "/groups"}}}, result)
end
test "user cannot delete group with non-existent slug", %{conn: conn} do
# This test verifies that delete action (if accessed via URL manipulation)
# cannot be performed with non-existent slug
# Note: Actual delete functionality will be tested when delete modal is implemented
non_existent_slug = "non-existent-group-slug"
# Attempting to access show page with non-existent slug should fail
result = live(conn, "/groups/#{non_existent_slug}")
assert match?({:error, {:redirect, %{to: "/groups"}}}, result) ||
match?({:error, {:live_redirect, %{to: "/groups"}}}, result)
end
end
describe "performance" do
test "member list is loaded efficiently (no N+1 queries)", %{conn: conn} do
group = Fixtures.group_fixture()
# Create multiple members
members = Enum.map(1..5, fn _ -> Fixtures.member_fixture() end)
system_actor = Mv.Helpers.SystemActor.get_system_actor()
# Add all members to group
Enum.each(members, fn member ->
Membership.create_member_group(%{member_id: member.id, group_id: group.id},
actor: system_actor
)
end)
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
# All members should be displayed
Enum.each(members, fn member ->
assert html =~ member.first_name || html =~ member.last_name
end)
end
test "slug lookup is efficient (uses unique_slug index)", %{conn: conn} do
group = Fixtures.group_fixture()
# Should use index for fast lookup
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
assert html =~ group.name
end
end
describe "navigation" do
test "back button navigates to groups list", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert {:error, {:live_redirect, %{to: to}}} =
view
|> element("a[aria-label*='Back'], button[aria-label*='Back']")
|> render_click()
assert to == "/groups"
end
test "edit button navigates to edit form", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert {:error, {:live_redirect, %{to: to}}} =
view
|> element("a[href*='edit'], button[href*='edit']")
|> render_click()
assert to == "/groups/#{group.slug}/edit"
end
end
end