308 lines
10 KiB
Elixir
308 lines
10 KiB
Elixir
defmodule Mv.Membership.CustomFieldValidationTest do
|
|
@moduledoc """
|
|
Tests for CustomField validation constraints.
|
|
|
|
Tests cover:
|
|
- Name length validation (max 100 characters)
|
|
- Name trimming
|
|
- Description length validation (max 500 characters)
|
|
- Description trimming
|
|
- Required vs optional fields
|
|
- Value type immutability (cannot be changed after creation)
|
|
"""
|
|
use Mv.DataCase, async: true
|
|
|
|
alias Mv.Membership.CustomField
|
|
|
|
setup do
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
%{actor: system_actor}
|
|
end
|
|
|
|
describe "name validation" do
|
|
test "accepts name with exactly 100 characters", %{actor: actor} do
|
|
name = String.duplicate("a", 100)
|
|
|
|
assert {:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: name,
|
|
value_type: :string
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert custom_field.name == name
|
|
assert String.length(custom_field.name) == 100
|
|
end
|
|
|
|
test "rejects name with 101 characters", %{actor: actor} do
|
|
name = String.duplicate("a", 101)
|
|
|
|
assert {:error, changeset} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: name,
|
|
value_type: :string
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert [%{field: :name, message: message}] = changeset.errors
|
|
assert message =~ "max" or message =~ "length" or message =~ "100"
|
|
end
|
|
|
|
test "trims whitespace from name", %{actor: actor} do
|
|
assert {:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: " test_field ",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert custom_field.name == "test_field"
|
|
end
|
|
|
|
test "rejects empty name", %{actor: actor} do
|
|
assert {:error, changeset} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert Enum.any?(changeset.errors, fn error -> error.field == :name end)
|
|
end
|
|
|
|
test "rejects nil name", %{actor: actor} do
|
|
assert {:error, changeset} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
value_type: :string
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert Enum.any?(changeset.errors, fn error -> error.field == :name end)
|
|
end
|
|
end
|
|
|
|
describe "description validation" do
|
|
test "accepts description with exactly 500 characters", %{actor: actor} do
|
|
description = String.duplicate("a", 500)
|
|
|
|
assert {:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "test_field",
|
|
value_type: :string,
|
|
description: description
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert custom_field.description == description
|
|
assert String.length(custom_field.description) == 500
|
|
end
|
|
|
|
test "rejects description with 501 characters", %{actor: actor} do
|
|
description = String.duplicate("a", 501)
|
|
|
|
assert {:error, changeset} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "test_field",
|
|
value_type: :string,
|
|
description: description
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert [%{field: :description, message: message}] = changeset.errors
|
|
assert message =~ "max" or message =~ "length" or message =~ "500"
|
|
end
|
|
|
|
test "trims whitespace from description", %{actor: actor} do
|
|
assert {:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "test_field",
|
|
value_type: :string,
|
|
description: " A nice description "
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert custom_field.description == "A nice description"
|
|
end
|
|
|
|
test "accepts nil description (optional field)", %{actor: actor} do
|
|
assert {:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "test_field",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert custom_field.description == nil
|
|
end
|
|
|
|
test "accepts empty description after trimming", %{actor: actor} do
|
|
assert {:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "test_field",
|
|
value_type: :string,
|
|
description: " "
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
# After trimming whitespace, becomes nil (empty strings are converted to nil)
|
|
assert custom_field.description == nil
|
|
end
|
|
end
|
|
|
|
describe "name uniqueness" do
|
|
test "rejects duplicate names", %{actor: actor} do
|
|
assert {:ok, _} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "unique_field",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert {:error, changeset} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "unique_field",
|
|
value_type: :integer
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert Enum.any?(changeset.errors, fn error -> error.field == :name end)
|
|
end
|
|
end
|
|
|
|
describe "value_type validation" do
|
|
test "accepts all valid value types", %{actor: actor} do
|
|
for value_type <- [:string, :integer, :boolean, :date, :email] do
|
|
assert {:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "field_#{value_type}",
|
|
value_type: value_type
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert custom_field.value_type == value_type
|
|
end
|
|
end
|
|
|
|
test "rejects invalid value type", %{actor: actor} do
|
|
assert {:error, changeset} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "invalid_field",
|
|
value_type: :invalid_type
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
assert [%{field: :value_type}] = changeset.errors
|
|
end
|
|
end
|
|
|
|
describe "value_type immutability" do
|
|
test "rejects attempt to change value_type after creation", %{actor: actor} do
|
|
# Create custom field with value_type :string
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "test_field",
|
|
value_type: :string
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
original_value_type = custom_field.value_type
|
|
assert original_value_type == :string
|
|
|
|
# Attempt to update value_type to :integer
|
|
assert {:error, %Ash.Error.Invalid{} = error} =
|
|
custom_field
|
|
|> Ash.Changeset.for_update(:update, %{
|
|
value_type: :integer
|
|
})
|
|
|> Ash.update(actor: actor)
|
|
|
|
# Verify error message contains expected text
|
|
error_message = Exception.message(error)
|
|
assert error_message =~ "cannot be changed" or error_message =~ "value_type"
|
|
|
|
# Reload and verify value_type remained unchanged
|
|
reloaded = Ash.get!(CustomField, custom_field.id, actor: actor)
|
|
assert reloaded.value_type == original_value_type
|
|
assert reloaded.value_type == :string
|
|
end
|
|
|
|
test "allows updating other fields while value_type remains unchanged", %{actor: actor} do
|
|
# Create custom field with value_type :string
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "test_field",
|
|
value_type: :string,
|
|
description: "Original description"
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
original_value_type = custom_field.value_type
|
|
assert original_value_type == :string
|
|
|
|
# Update other fields (name, description) without touching value_type
|
|
{:ok, updated_custom_field} =
|
|
custom_field
|
|
|> Ash.Changeset.for_update(:update, %{
|
|
name: "updated_name",
|
|
description: "Updated description"
|
|
})
|
|
|> Ash.update(actor: actor)
|
|
|
|
# Verify value_type remained unchanged
|
|
assert updated_custom_field.value_type == original_value_type
|
|
assert updated_custom_field.value_type == :string
|
|
# Verify other fields were updated
|
|
assert updated_custom_field.name == "updated_name"
|
|
assert updated_custom_field.description == "Updated description"
|
|
end
|
|
|
|
test "rejects value_type change even when other fields are updated", %{actor: actor} do
|
|
# Create custom field with value_type :boolean
|
|
{:ok, custom_field} =
|
|
CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "test_field",
|
|
value_type: :boolean
|
|
})
|
|
|> Ash.create(actor: actor)
|
|
|
|
original_value_type = custom_field.value_type
|
|
assert original_value_type == :boolean
|
|
|
|
# Attempt to update both name and value_type
|
|
assert {:error, %Ash.Error.Invalid{} = error} =
|
|
custom_field
|
|
|> Ash.Changeset.for_update(:update, %{
|
|
name: "updated_name",
|
|
value_type: :date
|
|
})
|
|
|> Ash.update(actor: actor)
|
|
|
|
# Verify error message
|
|
error_message = Exception.message(error)
|
|
assert error_message =~ "cannot be changed" or error_message =~ "value_type"
|
|
|
|
# Reload and verify value_type remained unchanged, but name was not updated either
|
|
reloaded = Ash.get!(CustomField, custom_field.id, actor: actor)
|
|
assert reloaded.value_type == original_value_type
|
|
assert reloaded.value_type == :boolean
|
|
assert reloaded.name == "test_field"
|
|
end
|
|
end
|
|
end
|