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