397 lines
11 KiB
Elixir
397 lines
11 KiB
Elixir
defmodule Mv.Membership.CustomFieldSlugTest do
|
|
@moduledoc """
|
|
Tests for automatic slug generation on CustomField resource.
|
|
|
|
This test suite verifies:
|
|
1. Slugs are automatically generated from the name attribute
|
|
2. Slugs are unique (cannot have duplicates)
|
|
3. Slugs are immutable (don't change when name changes)
|
|
4. Slugs handle various edge cases (unicode, special chars, etc.)
|
|
5. Slugs can be used for lookups
|
|
"""
|
|
use Mv.DataCase, async: true
|
|
|
|
alias Mv.Membership.CustomField
|
|
|
|
describe "automatic slug generation on create" do
|
|
test "generates slug from name with simple ASCII text" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Mobile Phone",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
assert custom_field.slug == "mobile-phone"
|
|
end
|
|
|
|
test "generates slug from name with German umlauts" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Café Müller",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
assert custom_field.slug == "cafe-muller"
|
|
end
|
|
|
|
test "generates slug with lowercase conversion" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "TEST NAME",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
assert custom_field.slug == "test-name"
|
|
end
|
|
|
|
test "generates slug by removing special characters" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "E-Mail & Address!",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
assert custom_field.slug == "e-mail-address"
|
|
end
|
|
|
|
test "generates slug by replacing multiple spaces with single hyphen" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Multiple Spaces",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
assert custom_field.slug == "multiple-spaces"
|
|
end
|
|
|
|
test "trims leading and trailing hyphens" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "-Test-",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
assert custom_field.slug == "test"
|
|
end
|
|
|
|
test "handles unicode characters properly (ß becomes ss)" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Straße",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
assert custom_field.slug == "strasse"
|
|
end
|
|
end
|
|
|
|
describe "slug uniqueness" do
|
|
test "prevents creating custom field with duplicate slug" do
|
|
# Create first custom field
|
|
{:ok, _custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
# 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()
|
|
|
|
assert Exception.message(error) =~ "has already been taken"
|
|
end
|
|
|
|
test "allows custom fields with different slugs" do
|
|
{:ok, custom_field1} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test One",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
{:ok, custom_field2} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test Two",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
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" do
|
|
{:ok, custom_field1} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test!!!",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
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()
|
|
|
|
# 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" 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()
|
|
|
|
# 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" do
|
|
# Create custom field
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Original Name",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
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()
|
|
|
|
# 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" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
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()
|
|
|
|
# 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)
|
|
assert reloaded.slug == "test"
|
|
end
|
|
end
|
|
|
|
describe "slug edge cases" do
|
|
test "handles very long names by truncating slug" do
|
|
# Create a name at the maximum length (100 chars)
|
|
long_name = String.duplicate("abcdefghij", 10)
|
|
# 100 characters exactly
|
|
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: long_name,
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
# Slug should be truncated to maximum 100 characters
|
|
assert String.length(custom_field.slug) <= 100
|
|
# Should be the full slugified version since name is exactly 100 chars
|
|
assert custom_field.slug == long_name
|
|
end
|
|
|
|
test "rejects name with only special characters" do
|
|
# When name contains only special characters, slug would be empty
|
|
# This should fail validation
|
|
assert {:error, %Ash.Error.Invalid{} = error} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "!!!",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
# Should fail because slug would be empty
|
|
error_message = Exception.message(error)
|
|
assert error_message =~ "Slug cannot be empty" or error_message =~ "is required"
|
|
end
|
|
|
|
test "handles mixed special characters and text" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test@#$%Name",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
# slugify keeps the hyphen between words
|
|
assert custom_field.slug == "test-name"
|
|
end
|
|
|
|
test "handles numbers in name" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Field 123 Test",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
assert custom_field.slug == "field-123-test"
|
|
end
|
|
|
|
test "handles consecutive hyphens in name" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test---Name",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
# Should reduce multiple hyphens to single hyphen
|
|
assert custom_field.slug == "test-name"
|
|
end
|
|
|
|
test "handles name with dots and underscores" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "test.field_name",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
# Dots and underscores should be handled (either kept or converted)
|
|
assert custom_field.slug =~ ~r/^[a-z0-9-]+$/
|
|
end
|
|
end
|
|
|
|
describe "slug in queries and responses" do
|
|
test "slug is included in struct after create" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
# Slug should be present in the struct
|
|
assert Map.has_key?(custom_field, :slug)
|
|
assert custom_field.slug != nil
|
|
end
|
|
|
|
test "can load custom field and slug is present" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
# Load it back
|
|
loaded_custom_field = Ash.get!(CustomField, custom_field.id)
|
|
|
|
assert loaded_custom_field.slug == "test"
|
|
end
|
|
|
|
test "slug is returned in list queries" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
custom_fields = Ash.read!(CustomField)
|
|
|
|
found = Enum.find(custom_fields, &(&1.id == custom_field.id))
|
|
assert found.slug == "test"
|
|
end
|
|
end
|
|
|
|
describe "slug-based lookup (future feature)" do
|
|
@tag :skip
|
|
test "can find custom field by slug" do
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test Field",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create()
|
|
|
|
# This test is for future implementation
|
|
# We might add a custom action like :by_slug
|
|
found = Ash.get!(CustomField, custom_field.slug, load: [:slug])
|
|
assert found.id == custom_field.id
|
|
end
|
|
end
|
|
end
|