mitgliederverwaltung/test/membership/custom_field_slug_test.exs
carla d34ff57531
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
refactor
2026-02-04 15:52:00 +01:00

212 lines
6.6 KiB
Elixir

defmodule Mv.Membership.CustomFieldSlugTest do
@moduledoc """
Tests for CustomField slug business rules only.
We test our business logic, not Ash/slugify implementation details:
- Slug is generated from name on create (one smoke test)
- Slug is unique (business rule)
- Slug is immutable (does not change when name is updated; cannot be set manually)
- Slug cannot be empty (rejects name with only special characters)
We do not test: slugify edge cases (umlauts, truncation, etc.) or Ash/Ecto struct/load behavior.
"""
use Mv.DataCase, async: true
alias Mv.Membership.CustomField
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
%{actor: system_actor}
end
describe "slug generation (business rule)" do
test "slug is generated from name on create", %{actor: actor} do
{:ok, custom_field} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Mobile Phone",
value_type: :string
})
|> Ash.create(actor: actor)
assert custom_field.slug == "mobile-phone"
end
end
describe "slug uniqueness" do
test "prevents creating custom field with duplicate slug", %{actor: actor} do
# Create first custom field
{:ok, _custom_field} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Test",
value_type: :string
})
|> Ash.create(actor: actor)
# Attempt to create second custom field with same slug (different case in name)
assert {:error, %Ash.Error.Invalid{} = error} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "test",
value_type: :integer
})
|> Ash.create(actor: actor)
assert Exception.message(error) =~ "has already been taken"
end
test "allows custom fields with different slugs", %{actor: actor} do
{:ok, custom_field1} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Test One",
value_type: :string
})
|> Ash.create(actor: actor)
{:ok, custom_field2} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Test Two",
value_type: :string
})
|> Ash.create(actor: actor)
assert custom_field1.slug == "test-one"
assert custom_field2.slug == "test-two"
assert custom_field1.slug != custom_field2.slug
end
test "prevents duplicate slugs when names differ only in special characters", %{actor: actor} do
{:ok, custom_field1} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Test!!!",
value_type: :string
})
|> Ash.create(actor: actor)
assert custom_field1.slug == "test"
# Second custom field with name that generates the same slug should fail
assert {:error, %Ash.Error.Invalid{} = error} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Test???",
value_type: :string
})
|> Ash.create(actor: actor)
# Should fail with uniqueness constraint error
assert Exception.message(error) =~ "has already been taken"
end
end
describe "slug immutability" do
test "slug cannot be manually set on create", %{actor: actor} do
# Attempting to set slug manually should fail because slug is not writable
result =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Test",
value_type: :string,
slug: "custom-slug"
})
|> Ash.create(actor: actor)
# Should fail because slug is not an accepted input
assert {:error, %Ash.Error.Invalid{}} = result
assert Exception.message(elem(result, 1)) =~ "No such input"
end
test "slug does not change when name is updated", %{actor: actor} do
# Create custom field
{:ok, custom_field} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Original Name",
value_type: :string
})
|> Ash.create(actor: actor)
original_slug = custom_field.slug
assert original_slug == "original-name"
# Update the name
{:ok, updated_custom_field} =
custom_field
|> Ash.Changeset.for_update(:update, %{
name: "New Different Name"
})
|> Ash.update(actor: actor)
# Slug should remain unchanged
assert updated_custom_field.slug == original_slug
assert updated_custom_field.slug == "original-name"
assert updated_custom_field.name == "New Different Name"
end
test "slug cannot be manually updated", %{actor: actor} do
{:ok, custom_field} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Test",
value_type: :string
})
|> Ash.create(actor: actor)
original_slug = custom_field.slug
assert original_slug == "test"
# Attempt to manually update slug should fail because slug is not writable
result =
custom_field
|> Ash.Changeset.for_update(:update, %{
slug: "new-slug"
})
|> Ash.update(actor: actor)
# Should fail because slug is not an accepted input
assert {:error, %Ash.Error.Invalid{}} = result
assert Exception.message(elem(result, 1)) =~ "No such input"
# Reload to verify slug hasn't changed
reloaded = Ash.get!(CustomField, custom_field.id, actor: actor)
assert reloaded.slug == "test"
end
end
describe "slug cannot be empty (business rule)" do
test "rejects name with only special characters", %{actor: actor} do
assert {:error, %Ash.Error.Invalid{} = error} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "!!!",
value_type: :string
})
|> Ash.create(actor: actor)
error_message = Exception.message(error)
assert error_message =~ "Slug cannot be empty" or error_message =~ "is required"
end
end
describe "slug-based lookup (future feature)" do
@tag :skip
test "can find custom field by slug", %{actor: actor} do
{:ok, custom_field} =
CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Test Field",
value_type: :string
})
|> Ash.create(actor: actor)
# This test is for future implementation
# We might add a custom action like :by_slug
found = Ash.get!(CustomField, custom_field.slug, load: [:slug], actor: actor)
assert found.id == custom_field.id
end
end
end