212 lines
6.6 KiB
Elixir
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
|