525 lines
18 KiB
Elixir
525 lines
18 KiB
Elixir
defmodule Mv.Membership.MemberRequiredCustomFieldsTest do
|
|
@moduledoc """
|
|
Tests for required custom fields validation.
|
|
|
|
Tests cover:
|
|
- Member creation without required custom field → error
|
|
- Member creation with empty required custom field (nil/empty string) → error
|
|
- Member creation with valid required custom field → success
|
|
- Member update: removing a required custom field value → error
|
|
- Boolean required custom field: false is valid, nil is invalid
|
|
"""
|
|
use Mv.DataCase, async: true
|
|
|
|
alias Mv.Membership
|
|
|
|
setup do
|
|
# Create required custom fields for different types
|
|
{:ok, required_string_field} =
|
|
Membership.CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "required_string",
|
|
value_type: :string,
|
|
required: true
|
|
})
|
|
|> Ash.create()
|
|
|
|
{:ok, required_integer_field} =
|
|
Membership.CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "required_integer",
|
|
value_type: :integer,
|
|
required: true
|
|
})
|
|
|> Ash.create()
|
|
|
|
{:ok, required_boolean_field} =
|
|
Membership.CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "required_boolean",
|
|
value_type: :boolean,
|
|
required: true
|
|
})
|
|
|> Ash.create()
|
|
|
|
{:ok, required_date_field} =
|
|
Membership.CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "required_date",
|
|
value_type: :date,
|
|
required: true
|
|
})
|
|
|> Ash.create()
|
|
|
|
{:ok, optional_field} =
|
|
Membership.CustomField
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "optional_string",
|
|
value_type: :string,
|
|
required: false
|
|
})
|
|
|> Ash.create()
|
|
|
|
%{
|
|
required_string_field: required_string_field,
|
|
required_integer_field: required_integer_field,
|
|
required_boolean_field: required_boolean_field,
|
|
required_date_field: required_date_field,
|
|
optional_field: optional_field
|
|
}
|
|
end
|
|
|
|
# Helper function to create all required custom fields with valid default values
|
|
defp all_required_custom_fields_with_defaults(%{
|
|
required_string_field: string_field,
|
|
required_integer_field: integer_field,
|
|
required_boolean_field: boolean_field,
|
|
required_date_field: date_field
|
|
}) do
|
|
[
|
|
%{
|
|
"custom_field_id" => string_field.id,
|
|
"value" => %{"_union_type" => "string", "_union_value" => "default"}
|
|
},
|
|
%{
|
|
"custom_field_id" => integer_field.id,
|
|
"value" => %{"_union_type" => "integer", "_union_value" => 0}
|
|
},
|
|
%{
|
|
"custom_field_id" => boolean_field.id,
|
|
"value" => %{"_union_type" => "boolean", "_union_value" => false}
|
|
},
|
|
%{
|
|
"custom_field_id" => date_field.id,
|
|
"value" => %{"_union_type" => "date", "_union_value" => ~D[2020-01-01]}
|
|
}
|
|
]
|
|
end
|
|
|
|
describe "create_member with required custom fields" do
|
|
@valid_attrs %{
|
|
first_name: "John",
|
|
last_name: "Doe",
|
|
email: "john@example.com"
|
|
}
|
|
|
|
test "fails when required custom field is missing", %{required_string_field: field} do
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, [])
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
|
assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
|
|
assert error_message(errors, :custom_field_values) =~ field.name
|
|
end
|
|
|
|
test "fails when required string custom field has nil value",
|
|
%{
|
|
required_string_field: field
|
|
} = context do
|
|
# Start with all required fields having valid values
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
if cfv["custom_field_id"] == field.id do
|
|
%{cfv | "value" => %{"_union_type" => "string", "_union_value" => nil}}
|
|
else
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
|
assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
|
|
assert error_message(errors, :custom_field_values) =~ field.name
|
|
end
|
|
|
|
test "fails when required string custom field has empty string value",
|
|
%{
|
|
required_string_field: field
|
|
} = context do
|
|
# Start with all required fields having valid values
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
if cfv["custom_field_id"] == field.id do
|
|
%{cfv | "value" => %{"_union_type" => "string", "_union_value" => ""}}
|
|
else
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
|
assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
|
|
assert error_message(errors, :custom_field_values) =~ field.name
|
|
end
|
|
|
|
test "fails when required string custom field has whitespace-only value",
|
|
%{
|
|
required_string_field: field
|
|
} = context do
|
|
# Start with all required fields having valid values
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
if cfv["custom_field_id"] == field.id do
|
|
%{cfv | "value" => %{"_union_type" => "string", "_union_value" => " "}}
|
|
else
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
|
assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
|
|
assert error_message(errors, :custom_field_values) =~ field.name
|
|
end
|
|
|
|
test "succeeds when required string custom field has valid value",
|
|
%{
|
|
required_string_field: field
|
|
} = context do
|
|
# Start with all required fields having valid values, then update the string field
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
if cfv["custom_field_id"] == field.id do
|
|
%{cfv | "value" => %{"_union_type" => "string", "_union_value" => "test value"}}
|
|
else
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:ok, _member} = Membership.create_member(attrs)
|
|
end
|
|
|
|
test "fails when required integer custom field has nil value",
|
|
%{
|
|
required_integer_field: field
|
|
} = context do
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
if cfv["custom_field_id"] == field.id do
|
|
%{cfv | "value" => %{"_union_type" => "integer", "_union_value" => nil}}
|
|
else
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
|
assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
|
|
assert error_message(errors, :custom_field_values) =~ field.name
|
|
end
|
|
|
|
test "succeeds when required integer custom field has zero value",
|
|
%{
|
|
required_integer_field: _field
|
|
} = context do
|
|
custom_field_values = all_required_custom_fields_with_defaults(context)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:ok, _member} = Membership.create_member(attrs)
|
|
end
|
|
|
|
test "succeeds when required integer custom field has positive value",
|
|
%{
|
|
required_integer_field: field
|
|
} = context do
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
if cfv["custom_field_id"] == field.id do
|
|
%{cfv | "value" => %{"_union_type" => "integer", "_union_value" => 42}}
|
|
else
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:ok, _member} = Membership.create_member(attrs)
|
|
end
|
|
|
|
test "fails when required boolean custom field has nil value",
|
|
%{
|
|
required_boolean_field: field
|
|
} = context do
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
if cfv["custom_field_id"] == field.id do
|
|
%{cfv | "value" => %{"_union_type" => "boolean", "_union_value" => nil}}
|
|
else
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
|
assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
|
|
assert error_message(errors, :custom_field_values) =~ field.name
|
|
end
|
|
|
|
test "succeeds when required boolean custom field has false value",
|
|
%{
|
|
required_boolean_field: _field
|
|
} = context do
|
|
custom_field_values = all_required_custom_fields_with_defaults(context)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:ok, _member} = Membership.create_member(attrs)
|
|
end
|
|
|
|
test "succeeds when required boolean custom field has true value",
|
|
%{
|
|
required_boolean_field: field
|
|
} = context do
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
if cfv["custom_field_id"] == field.id do
|
|
%{cfv | "value" => %{"_union_type" => "boolean", "_union_value" => true}}
|
|
else
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:ok, _member} = Membership.create_member(attrs)
|
|
end
|
|
|
|
test "fails when required date custom field has nil value",
|
|
%{
|
|
required_date_field: field
|
|
} = context do
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
if cfv["custom_field_id"] == field.id do
|
|
%{cfv | "value" => %{"_union_type" => "date", "_union_value" => nil}}
|
|
else
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
|
assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
|
|
assert error_message(errors, :custom_field_values) =~ field.name
|
|
end
|
|
|
|
test "succeeds when required date custom field has valid date value",
|
|
%{
|
|
required_date_field: _field
|
|
} = context do
|
|
custom_field_values = all_required_custom_fields_with_defaults(context)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:ok, _member} = Membership.create_member(attrs)
|
|
end
|
|
|
|
test "succeeds when multiple required custom fields are provided",
|
|
%{
|
|
required_string_field: string_field,
|
|
required_integer_field: integer_field,
|
|
required_boolean_field: boolean_field
|
|
} = context do
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
cond do
|
|
cfv["custom_field_id"] == string_field.id ->
|
|
%{cfv | "value" => %{"_union_type" => "string", "_union_value" => "test"}}
|
|
|
|
cfv["custom_field_id"] == integer_field.id ->
|
|
%{cfv | "value" => %{"_union_type" => "integer", "_union_value" => 42}}
|
|
|
|
cfv["custom_field_id"] == boolean_field.id ->
|
|
%{cfv | "value" => %{"_union_type" => "boolean", "_union_value" => true}}
|
|
|
|
true ->
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:ok, _member} = Membership.create_member(attrs)
|
|
end
|
|
|
|
test "fails when one of multiple required custom fields is missing",
|
|
%{
|
|
required_string_field: string_field,
|
|
required_integer_field: integer_field
|
|
} = context do
|
|
# Provide only string field, missing integer, boolean, and date
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.filter(fn cfv ->
|
|
cfv["custom_field_id"] == string_field.id
|
|
end)
|
|
|> Enum.map(fn cfv ->
|
|
%{cfv | "value" => %{"_union_type" => "string", "_union_value" => "test"}}
|
|
end)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
|
assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
|
|
assert error_message(errors, :custom_field_values) =~ integer_field.name
|
|
end
|
|
|
|
test "succeeds when optional custom field is missing", %{} = context do
|
|
# Provide all required fields, but no optional field
|
|
custom_field_values = all_required_custom_fields_with_defaults(context)
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:ok, _member} = Membership.create_member(attrs)
|
|
end
|
|
|
|
test "succeeds when optional custom field has nil value",
|
|
%{optional_field: field} = context do
|
|
# Provide all required fields plus optional field with nil
|
|
custom_field_values =
|
|
all_required_custom_fields_with_defaults(context) ++
|
|
[
|
|
%{
|
|
"custom_field_id" => field.id,
|
|
"value" => %{"_union_type" => "string", "_union_value" => nil}
|
|
}
|
|
]
|
|
|
|
attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)
|
|
|
|
assert {:ok, _member} = Membership.create_member(attrs)
|
|
end
|
|
end
|
|
|
|
describe "update_member with required custom fields" do
|
|
test "fails when removing a required custom field value",
|
|
%{
|
|
required_string_field: field
|
|
} = context do
|
|
# Create member with all required custom fields
|
|
custom_field_values = all_required_custom_fields_with_defaults(context)
|
|
|
|
{:ok, member} =
|
|
Membership.create_member(%{
|
|
first_name: "John",
|
|
last_name: "Doe",
|
|
email: "john@example.com",
|
|
custom_field_values: custom_field_values
|
|
})
|
|
|
|
# Try to update without the required custom field
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} =
|
|
Membership.update_member(member, %{custom_field_values: []})
|
|
|
|
assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
|
|
assert error_message(errors, :custom_field_values) =~ field.name
|
|
end
|
|
|
|
test "fails when setting required custom field value to empty",
|
|
%{
|
|
required_string_field: field
|
|
} = context do
|
|
# Create member with all required custom fields
|
|
custom_field_values = all_required_custom_fields_with_defaults(context)
|
|
|
|
{:ok, member} =
|
|
Membership.create_member(%{
|
|
first_name: "John",
|
|
last_name: "Doe",
|
|
email: "john@example.com",
|
|
custom_field_values: custom_field_values
|
|
})
|
|
|
|
# Try to update with empty value for the string field
|
|
updated_custom_field_values =
|
|
all_required_custom_fields_with_defaults(context)
|
|
|> Enum.map(fn cfv ->
|
|
if cfv["custom_field_id"] == field.id do
|
|
%{cfv | "value" => %{"_union_type" => "string", "_union_value" => ""}}
|
|
else
|
|
cfv
|
|
end
|
|
end)
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} =
|
|
Membership.update_member(member, %{
|
|
custom_field_values: updated_custom_field_values
|
|
})
|
|
|
|
assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
|
|
assert error_message(errors, :custom_field_values) =~ field.name
|
|
end
|
|
|
|
test "succeeds when updating required custom field to valid value",
|
|
%{
|
|
required_string_field: field
|
|
} = context do
|
|
# Create member with all required custom fields
|
|
custom_field_values = all_required_custom_fields_with_defaults(context)
|
|
|
|
{:ok, member} =
|
|
Membership.create_member(%{
|
|
first_name: "John",
|
|
last_name: "Doe",
|
|
email: "john@example.com",
|
|
custom_field_values: custom_field_values
|
|
})
|
|
|
|
# Load existing custom field values to get their IDs
|
|
{:ok, member_with_cfvs} = Ash.load(member, :custom_field_values)
|
|
|
|
# Update with new valid value for the string field, using existing IDs
|
|
updated_custom_field_values =
|
|
member_with_cfvs.custom_field_values
|
|
|> Enum.map(fn cfv ->
|
|
if cfv.custom_field_id == field.id do
|
|
%{
|
|
"id" => cfv.id,
|
|
"custom_field_id" => cfv.custom_field_id,
|
|
"value" => %{"_union_type" => "string", "_union_value" => "new value"}
|
|
}
|
|
else
|
|
# Keep other fields as they are
|
|
value_type = Atom.to_string(cfv.value.type)
|
|
actual_value = cfv.value.value
|
|
|
|
%{
|
|
"id" => cfv.id,
|
|
"custom_field_id" => cfv.custom_field_id,
|
|
"value" => %{"_union_type" => value_type, "_union_value" => actual_value}
|
|
}
|
|
end
|
|
end)
|
|
|
|
assert {:ok, _updated_member} =
|
|
Membership.update_member(member, %{
|
|
custom_field_values: updated_custom_field_values
|
|
})
|
|
end
|
|
end
|
|
|
|
# Helper function for error evaluation
|
|
defp error_message(errors, field) do
|
|
errors
|
|
|> Enum.filter(fn err -> Map.get(err, :field) == field end)
|
|
|> Enum.map_join(" ", &Map.get(&1, :message, ""))
|
|
end
|
|
end
|