test: add tests for join form settings
This commit is contained in:
parent
5deb102e45
commit
b7a83d9298
2 changed files with 284 additions and 0 deletions
|
|
@ -809,6 +809,9 @@ end
|
||||||
- **PR review follow-ups (Join confirmation):** Join confirmation email uses `Mailer.deliver/1` and returns `{:ok, email}` \| `{:error, reason}`; domain logs delivery errors but still returns `{:ok, request}` so the user sees success. Comment in `submit_join_request/2` clarifies that the raw token is hashed by `JoinRequest.Changes.SetConfirmationToken`. Cleanup task uses `Ash.bulk_destroy` and logs partial errors without halting. Layout uses assigns `app_name` and `locale` (from config/Gettext) instead of hardcoded "Mila" and `lang="de"`. Production `runtime.exs` sets `:mail_from` from ENV (`MAIL_FROM_NAME`, `MAIL_FROM_EMAIL`). Layout reference unified to `"layout.html"`; redundant `put_layout` removed from senders.
|
- **PR review follow-ups (Join confirmation):** Join confirmation email uses `Mailer.deliver/1` and returns `{:ok, email}` \| `{:error, reason}`; domain logs delivery errors but still returns `{:ok, request}` so the user sees success. Comment in `submit_join_request/2` clarifies that the raw token is hashed by `JoinRequest.Changes.SetConfirmationToken`. Cleanup task uses `Ash.bulk_destroy` and logs partial errors without halting. Layout uses assigns `app_name` and `locale` (from config/Gettext) instead of hardcoded "Mila" and `lang="de"`. Production `runtime.exs` sets `:mail_from` from ENV (`MAIL_FROM_NAME`, `MAIL_FROM_EMAIL`). Layout reference unified to `"layout.html"`; redundant `put_layout` removed from senders.
|
||||||
- Tests: `join_request_test.exs`, `join_request_submit_email_test.exs`, `join_confirm_controller_test.exs` – all pass.
|
- Tests: `join_request_test.exs`, `join_request_submit_email_test.exs`, `join_confirm_controller_test.exs` – all pass.
|
||||||
|
|
||||||
|
**Subtask 3 – Admin: Join form settings (TDD tests only):**
|
||||||
|
- Test file: `test/membership/setting_join_form_test.exs` – TDD tests for join form settings (persistence, validation, allowlist, defaults, robustness). Tests are red until Setting gains `join_form_enabled`, `join_form_field_ids`, `join_form_field_required` and `Mv.Membership.get_join_form_allowlist/0` is implemented. No functionality implemented yet.
|
||||||
|
|
||||||
### Test Data Management
|
### Test Data Management
|
||||||
|
|
||||||
**Seed Data:**
|
**Seed Data:**
|
||||||
|
|
|
||||||
281
test/membership/setting_join_form_test.exs
Normal file
281
test/membership/setting_join_form_test.exs
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
defmodule Mv.Membership.SettingJoinFormTest do
|
||||||
|
@moduledoc """
|
||||||
|
TDD tests for Join Form Settings (onboarding-join-concept subtask 3).
|
||||||
|
|
||||||
|
These tests define the expected API and behaviour for the "Onboarding / Join" section
|
||||||
|
in global settings. No functionality is implemented yet; tests are expected to fail
|
||||||
|
(red) until:
|
||||||
|
|
||||||
|
- Setting resource has attributes: `join_form_enabled`, `join_form_field_ids`,
|
||||||
|
`join_form_field_required`, plus validations and accept in the update action.
|
||||||
|
- `Mv.Membership.get_join_form_allowlist/0` is implemented and returns the allowlist
|
||||||
|
for the public join form (subtask 4).
|
||||||
|
"""
|
||||||
|
use Mv.DataCase, async: false
|
||||||
|
|
||||||
|
alias Mv.Membership
|
||||||
|
alias Mv.Helpers.SystemActor
|
||||||
|
alias Mv.Constants
|
||||||
|
|
||||||
|
setup do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
saved_enabled = Map.get(settings, :join_form_enabled)
|
||||||
|
saved_ids = Map.get(settings, :join_form_field_ids)
|
||||||
|
saved_required = Map.get(settings, :join_form_field_required)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
{:ok, s} = Membership.get_settings()
|
||||||
|
attrs = %{}
|
||||||
|
attrs = if saved_enabled != nil, do: Map.put(attrs, :join_form_enabled, saved_enabled), else: attrs
|
||||||
|
attrs = if saved_ids != nil, do: Map.put(attrs, :join_form_field_ids, saved_ids || []), else: attrs
|
||||||
|
attrs =
|
||||||
|
if saved_required != nil,
|
||||||
|
do: Map.put(attrs, :join_form_field_required, saved_required || %{}),
|
||||||
|
else: attrs
|
||||||
|
|
||||||
|
if attrs != %{} do
|
||||||
|
Membership.update_settings(s, attrs)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_join_form_settings(settings, attrs) do
|
||||||
|
Membership.update_settings(settings, attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp error_message(errors, field) when is_atom(field) do
|
||||||
|
errors
|
||||||
|
|> Enum.filter(fn err -> Map.get(err, :field) == field end)
|
||||||
|
|> Enum.map(&Map.get(&1, :message, ""))
|
||||||
|
|> List.first() || ""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ---- 1. Persistence and loading ----
|
||||||
|
|
||||||
|
describe "join form settings persistence and loading" do
|
||||||
|
test "save and load join_form_enabled plus field selection and required flags returns same config" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
attrs = %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: ["email", "first_name"],
|
||||||
|
join_form_field_required: %{"email" => true, "first_name" => false}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, updated} = update_join_form_settings(settings, attrs)
|
||||||
|
assert updated.join_form_enabled == true
|
||||||
|
assert updated.join_form_field_ids == ["email", "first_name"]
|
||||||
|
assert updated.join_form_field_required["email"] == true
|
||||||
|
assert updated.join_form_field_required["first_name"] == false
|
||||||
|
|
||||||
|
{:ok, reloaded} = Membership.get_settings()
|
||||||
|
assert reloaded.join_form_enabled == true
|
||||||
|
assert reloaded.join_form_field_ids == ["email", "first_name"]
|
||||||
|
assert reloaded.join_form_field_required["email"] == true
|
||||||
|
assert reloaded.join_form_field_required["first_name"] == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "repeated save with changed field list overwrites config without leftovers" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
assert {:ok, _} = update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: ["email", "first_name"],
|
||||||
|
join_form_field_required: %{"email" => true, "first_name" => false}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, updated} = update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: ["email", "last_name"],
|
||||||
|
join_form_field_required: %{"email" => true, "last_name" => false}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert updated.join_form_field_ids == ["email", "last_name"]
|
||||||
|
assert Map.has_key?(updated.join_form_field_required, "last_name")
|
||||||
|
refute Map.has_key?(updated.join_form_field_required, "first_name")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ---- 2. Validation ----
|
||||||
|
|
||||||
|
describe "join form settings validation" do
|
||||||
|
test "only existing member fields or custom field ids are accepted; unknown field names rejected or sanitized" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
|
||||||
|
result = update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: ["email", "not_a_member_field"],
|
||||||
|
join_form_field_required: %{"email" => true, "not_a_member_field" => false}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Until attributes exist we get NoSuchInput; once implemented we expect validation error
|
||||||
|
assert {:error, _} = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "config without email is rejected or email is auto-added and required" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
|
||||||
|
result = update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: ["first_name", "last_name"],
|
||||||
|
join_form_field_required: %{"first_name" => true, "last_name" => false}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Either rejected or, when loaded, email must be present and required
|
||||||
|
case result do
|
||||||
|
{:error, _} ->
|
||||||
|
:ok
|
||||||
|
{:ok, updated} ->
|
||||||
|
assert "email" in updated.join_form_field_ids
|
||||||
|
assert updated.join_form_field_required["email"] == true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "required false for email is ignored or forced to true when saved" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
|
||||||
|
{:ok, updated} = update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: ["email", "first_name"],
|
||||||
|
join_form_field_required: %{"email" => false, "first_name" => false}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert updated.join_form_field_required["email"] == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "required flag for field not in join_form_field_ids is rejected or dropped" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
|
||||||
|
result = update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: ["email"],
|
||||||
|
join_form_field_required: %{"email" => true, "first_name" => true}
|
||||||
|
})
|
||||||
|
|
||||||
|
case result do
|
||||||
|
{:error, _} ->
|
||||||
|
:ok
|
||||||
|
{:ok, updated} ->
|
||||||
|
refute Map.has_key?(updated.join_form_field_required, "first_name")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ---- 3. Allowlist for join form ----
|
||||||
|
|
||||||
|
describe "join form allowlist" do
|
||||||
|
test "allowlist returns configured fields with required/optional when join form enabled" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: ["email", "first_name"],
|
||||||
|
join_form_field_required: %{"email" => true, "first_name" => false}
|
||||||
|
})
|
||||||
|
|
||||||
|
allowlist = Membership.get_join_form_allowlist()
|
||||||
|
|
||||||
|
assert length(allowlist) == 2
|
||||||
|
email_entry = Enum.find(allowlist, &( &1.id == "email" ))
|
||||||
|
first_name_entry = Enum.find(allowlist, &( &1.id == "first_name" ))
|
||||||
|
assert email_entry.required == true
|
||||||
|
assert first_name_entry.required == false
|
||||||
|
assert email_entry.type == :member_field
|
||||||
|
assert first_name_entry.type == :member_field
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allowlist returns empty or defined default when join form disabled" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: false,
|
||||||
|
join_form_field_ids: ["email", "first_name"],
|
||||||
|
join_form_field_required: %{"email" => true, "first_name" => false}
|
||||||
|
})
|
||||||
|
|
||||||
|
allowlist = Membership.get_join_form_allowlist()
|
||||||
|
|
||||||
|
assert allowlist == []
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :requires_custom_field
|
||||||
|
test "allowlist distinguishes member fields and custom field identifiers" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
actor = SystemActor.get_system_actor()
|
||||||
|
{:ok, cf} =
|
||||||
|
Membership.create_custom_field(
|
||||||
|
%{name: "join_cf_#{System.unique_integer([:positive])}", value_type: :string},
|
||||||
|
actor: actor
|
||||||
|
)
|
||||||
|
update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: ["email", cf.id],
|
||||||
|
join_form_field_required: %{"email" => true, cf.id => false}
|
||||||
|
})
|
||||||
|
|
||||||
|
allowlist = Membership.get_join_form_allowlist()
|
||||||
|
|
||||||
|
email_entry = Enum.find(allowlist, &( &1.id == "email" ))
|
||||||
|
cf_entry = Enum.find(allowlist, &( &1.id == cf.id ))
|
||||||
|
assert email_entry.type == :member_field
|
||||||
|
assert cf_entry.type == :custom_field
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ---- 4. Defaults and fallback ----
|
||||||
|
|
||||||
|
describe "join form defaults and fallback" do
|
||||||
|
test "when no join settings stored, allowlist returns defined default (e.g. disabled, empty list)" do
|
||||||
|
allowlist = Membership.get_join_form_allowlist()
|
||||||
|
# Default: join form disabled → empty allowlist
|
||||||
|
assert is_list(allowlist)
|
||||||
|
assert allowlist == [] || Enum.all?(allowlist, &(is_map(&1) and Map.has_key?(&1, :id)))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "existing settings without join keys load correctly; new join keys get defaults" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
# Ensure other attributes still load
|
||||||
|
assert Map.has_key?(settings, :club_name)
|
||||||
|
# When join keys exist they have sensible defaults
|
||||||
|
join_enabled = Map.get(settings, :join_form_enabled)
|
||||||
|
join_ids = Map.get(settings, :join_form_field_ids)
|
||||||
|
if join_enabled != nil, do: assert(is_boolean(join_enabled))
|
||||||
|
if join_ids != nil, do: assert(is_list(join_ids))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ---- 5. Authorization (backend: settings update requires authorized actor when policy enforced) ----
|
||||||
|
# Authorization for the settings page is covered by GlobalSettingsLive and page-permission tests.
|
||||||
|
# If the domain later requires an actor for update_settings, tests here would pass an actor.
|
||||||
|
|
||||||
|
# ---- 6. Robustness / edge cases ----
|
||||||
|
|
||||||
|
describe "join form settings robustness" do
|
||||||
|
test "invalid or unexpected payload structure yields clean error or ignores unknown keys" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
|
||||||
|
result = update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: "not_a_list",
|
||||||
|
join_form_field_required: %{}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert match?({:error, _}, result) or
|
||||||
|
(match?({:ok, _}, result) && elem(result, 1).join_form_field_ids != "not_a_list")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "larger but reasonable number of fields saves and loads without error" do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
all_member = Constants.member_fields() |> Enum.map(&to_string/1)
|
||||||
|
required_map = Map.new(all_member, fn f -> {f, f == "email"} end)
|
||||||
|
|
||||||
|
assert {:ok, updated} = update_join_form_settings(settings, %{
|
||||||
|
join_form_enabled: true,
|
||||||
|
join_form_field_ids: all_member,
|
||||||
|
join_form_field_required: required_map
|
||||||
|
})
|
||||||
|
|
||||||
|
assert length(updated.join_form_field_ids) == length(all_member)
|
||||||
|
{:ok, reloaded} = Membership.get_settings()
|
||||||
|
assert length(reloaded.join_form_field_ids) == length(all_member)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue