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