Tests: member required fields, setting, member field live, sync_contact

Add tests for required validation, update_single_member_field, form
required map. Add street/postal_code/city to sync_contact when Vereinfacht configured.
This commit is contained in:
Moritz 2026-02-23 22:11:15 +01:00
parent 27b9cbe814
commit d44c5bdf94
Signed by: moritz
GPG key ID: 1020A035E5DD0824
5 changed files with 208 additions and 34 deletions

View file

@ -92,6 +92,67 @@ defmodule Mv.Membership.MemberTest do
end end
end end
describe "Settings-driven required fields" do
@valid_attrs %{
first_name: "John",
last_name: "Doe",
email: "john@example.com"
}
test "when first_name is required in settings, create without first_name fails", %{
actor: actor
} do
{:ok, settings} = Membership.get_settings()
{:ok, _} =
Membership.update_single_member_field(settings,
field: "first_name",
show_in_overview: true,
required: true
)
attrs = Map.delete(@valid_attrs, :first_name)
assert {:error, %Ash.Error.Invalid{errors: errors}} =
Membership.create_member(attrs, actor: actor)
assert error_message(errors, :first_name) =~ "can't be blank"
# Reset so other tests (e.g. "First name is optional") are not affected
{:ok, settings} = Membership.get_settings()
Membership.update_single_member_field(settings,
field: "first_name",
show_in_overview: true,
required: false
)
end
test "when first_name is required in settings, create with first_name succeeds", %{
actor: actor
} do
{:ok, settings} = Membership.get_settings()
{:ok, _} =
Membership.update_single_member_field(settings,
field: "first_name",
show_in_overview: true,
required: true
)
assert {:ok, _member} = Membership.create_member(@valid_attrs, actor: actor)
# Reset
{:ok, settings} = Membership.get_settings()
Membership.update_single_member_field(settings,
field: "first_name",
show_in_overview: true,
required: false
)
end
end
describe "Authorization" do describe "Authorization" do
@valid_attrs %{ @valid_attrs %{
first_name: "John", first_name: "John",

View file

@ -39,6 +39,65 @@ defmodule Mv.Membership.SettingTest do
assert error_message(errors, :club_name) =~ "must be present" assert error_message(errors, :club_name) =~ "must be present"
end end
test "can update and read member_field_required" do
{:ok, settings} = Membership.get_settings()
required_config = %{"first_name" => true, "last_name" => true}
assert {:ok, updated} =
Membership.update_settings(settings, %{member_field_required: required_config})
assert updated.member_field_required["first_name"] == true
assert updated.member_field_required["last_name"] == true
end
test "member_field_required rejects invalid keys" do
{:ok, settings} = Membership.get_settings()
assert {:error, %Ash.Error.Invalid{errors: errors}} =
Membership.update_settings(settings, %{
member_field_required: %{"invalid_field" => true}
})
assert error_message(errors, :member_field_required) =~ "Invalid member field"
end
test "member_field_required rejects non-boolean values" do
{:ok, settings} = Membership.get_settings()
assert {:error, %Ash.Error.Invalid{errors: errors}} =
Membership.update_settings(settings, %{
member_field_required: %{"first_name" => "yes"}
})
assert error_message(errors, :member_field_required) =~ "must be booleans"
end
test "update_single_member_field updates both visibility and required" do
{:ok, settings} = Membership.get_settings()
assert {:ok, updated} =
Membership.update_single_member_field(settings,
field: "first_name",
show_in_overview: true,
required: true
)
assert updated.member_field_visibility["first_name"] == true
assert updated.member_field_required["first_name"] == true
# Update same field to required: false
assert {:ok, updated2} =
Membership.update_single_member_field(updated,
field: "first_name",
show_in_overview: false,
required: false
)
assert updated2.member_field_visibility["first_name"] == false
assert updated2.member_field_required["first_name"] == false
end
end end
# Helper function to extract error messages # Helper function to extract error messages

View file

@ -54,7 +54,10 @@ defmodule Mv.Vereinfacht.Changes.SyncContactTest do
attrs = %{ attrs = %{
first_name: "API", first_name: "API",
last_name: "Test", last_name: "Test",
email: "api_test_#{System.unique_integer([:positive])}@example.com" email: "api_test_#{System.unique_integer([:positive])}@example.com",
street: "Test St",
postal_code: "12345",
city: "Test City"
} }
assert {:ok, member} = Membership.create_member(attrs, actor: system_actor) assert {:ok, member} = Membership.create_member(attrs, actor: system_actor)
@ -66,7 +69,14 @@ defmodule Mv.Vereinfacht.Changes.SyncContactTest do
test "update_member succeeds and after_transaction runs without error (API may fail)" do test "update_member succeeds and after_transaction runs without error (API may fail)" do
set_vereinfacht_env() set_vereinfacht_env()
member = Mv.Fixtures.member_fixture()
member =
Mv.Fixtures.member_fixture(%{
street: "Test St",
postal_code: "12345",
city: "Test City"
})
system_actor = Mv.Helpers.SystemActor.get_system_actor() system_actor = Mv.Helpers.SystemActor.get_system_actor()
assert {:ok, updated} = assert {:ok, updated} =

View file

@ -5,8 +5,8 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
Tests cover: Tests cover:
- Rendering all member fields from Mv.Constants.member_fields() - Rendering all member fields from Mv.Constants.member_fields()
- Displaying show_in_overview status as badge (Yes/No) - Displaying show_in_overview status as badge (Yes/No)
- Displaying required status for required fields (first_name, last_name, email) - Displaying required status from settings.member_field_required (email is always required)
- Current status is displayed based on settings.member_field_visibility - Current status is displayed based on settings.member_field_visibility and member_field_required
- Default status is "Yes" (visible) when not configured in settings - Default status is "Yes" (visible) when not configured in settings
""" """
use MvWeb.ConnCase, async: false use MvWeb.ConnCase, async: false
@ -45,11 +45,10 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
assert html =~ "badge" or html =~ "Yes" or html =~ "No" assert html =~ "badge" or html =~ "Yes" or html =~ "No"
end end
test "displays required status for required fields", %{conn: conn} do test "displays required status column", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings") {:ok, _view, html} = live(conn, ~p"/settings")
# Required fields: first_name, last_name, email # Should have "Required" column; email is always required
# Should have "Required" column or indicator
assert html =~ "Required" or html =~ "required" assert html =~ "Required" or html =~ "required"
end end
@ -85,40 +84,46 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
end end
describe "required fields" do describe "required fields" do
test "marks first_name as required", %{conn: conn} do test "marks email as required (always from settings)", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings") {:ok, _view, html} = live(conn, ~p"/settings")
# first_name should be marked as required # Email is always required
assert html =~ "first_name" or html =~ "First name"
# Should have required indicator
assert html =~ "required" or html =~ "Required"
end
test "marks last_name as required", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings")
# last_name should be marked as required
assert html =~ "last_name" or html =~ "Last name"
# Should have required indicator
assert html =~ "required" or html =~ "Required"
end
test "marks email as required", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings")
# email should be marked as required
assert html =~ "email" or html =~ "Email" assert html =~ "email" or html =~ "Email"
# Should have required indicator assert html =~ "Required" or html =~ "Optional"
assert html =~ "required" or html =~ "Required"
end end
test "does not mark optional fields as required", %{conn: conn} do test "when first_name is set required in settings, table shows Required", %{conn: conn} do
{:ok, settings} = Membership.get_settings()
{:ok, _} =
Membership.update_single_member_field(settings,
field: "first_name",
show_in_overview: true,
required: true
)
{:ok, _view, html} = live(conn, ~p"/settings") {:ok, _view, html} = live(conn, ~p"/settings")
# Optional fields should not have required indicator # First name row should show Required (and Optional for others)
# Check that street (optional) doesn't have required badge assert html =~ "First name" or html =~ "first_name"
# This test verifies that only required fields show the indicator assert html =~ "Required"
assert html =~ "street" or html =~ "Street"
# Reset
{:ok, settings} = Membership.get_settings()
Membership.update_single_member_field(settings,
field: "first_name",
show_in_overview: true,
required: false
)
end
test "optional fields show Optional when not required in settings", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings")
# Email is required; other fields default to optional
assert html =~ "Optional"
assert html =~ "Required"
end end
end end
end end

View file

@ -74,6 +74,45 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
html =~ "Please correct" or html =~ "Bitte korrigieren" html =~ "Please correct" or html =~ "Bitte korrigieren"
end end
@tag :ui
test "shows validation error when settings-required field is missing", %{conn: conn} do
{:ok, settings} = Mv.Membership.get_settings()
{:ok, _} =
Mv.Membership.update_single_member_field(settings,
field: "first_name",
show_in_overview: true,
required: true
)
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members/new")
# Submit without first_name (required in settings)
form_data = %{
"member[last_name]" => "User",
"member[email]" => "newuser#{System.unique_integer([:positive])}@example.com"
}
html =
view
|> form("#member-form", form_data)
|> render_submit()
assert html =~ "error" or html =~ "Error" or html =~ "Fehler" or
html =~ "first_name" or html =~ "First name" or html =~ "can't be blank" or
html =~ "darf nicht leer sein"
# Reset settings
{:ok, settings} = Mv.Membership.get_settings()
Mv.Membership.update_single_member_field(settings,
field: "first_name",
show_in_overview: true,
required: false
)
end
test "shows flash message when member update fails", %{conn: conn} do test "shows flash message when member update fails", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor() system_actor = Mv.Helpers.SystemActor.get_system_actor()