diff --git a/lib/membership/member.ex b/lib/membership/member.ex index 73820f6..286bb1a 100644 --- a/lib/membership/member.ex +++ b/lib/membership/member.ex @@ -354,7 +354,15 @@ defmodule Mv.Membership.Member do Enum.reduce(custom_field_values_arg, %{}, fn cfv, acc -> custom_field_id = Map.get(cfv, "custom_field_id") value_map = Map.get(cfv, "value", %{}) - actual_value = Map.get(value_map, "value") + + # Support both "value" and "_union_value" keys, without using || to preserve false values + actual_value = + cond do + Map.has_key?(value_map, "value") -> Map.get(value_map, "value") + Map.has_key?(value_map, "_union_value") -> Map.get(value_map, "_union_value") + true -> nil + end + Map.put(acc, custom_field_id, actual_value) end) end diff --git a/test/membership/member_required_custom_fields_test.exs b/test/membership/member_required_custom_fields_test.exs index 7467152..747cebd 100644 --- a/test/membership/member_required_custom_fields_test.exs +++ b/test/membership/member_required_custom_fields_test.exs @@ -69,6 +69,33 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do } 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", @@ -84,15 +111,20 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do assert error_message(errors, :custom_field_values) =~ field.name end - test "fails when required string custom field has nil value", %{ - required_string_field: field - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "string", "value" => nil} - } - ] + 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) @@ -101,15 +133,20 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do 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 - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "string", "value" => ""} - } - ] + 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) @@ -118,15 +155,20 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do 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 - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "string", "value" => " "} - } - ] + 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) @@ -135,30 +177,39 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do assert error_message(errors, :custom_field_values) =~ field.name end - test "succeeds when required string custom field has valid value", %{ - required_string_field: field - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "string", "value" => "test value"} - } - ] + 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 - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "integer", "value" => nil} - } - ] + 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) @@ -167,45 +218,49 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do assert error_message(errors, :custom_field_values) =~ field.name end - test "succeeds when required integer custom field has zero value", %{ - required_integer_field: field - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "integer", "value" => 0} - } - ] + 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 - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "integer", "value" => 42} - } - ] + 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 - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "boolean", "value" => nil} - } - ] + 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) @@ -214,45 +269,49 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do assert error_message(errors, :custom_field_values) =~ field.name end - test "succeeds when required boolean custom field has false value", %{ - required_boolean_field: field - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "boolean", "value" => false} - } - ] + 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 - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "boolean", "value" => true} - } - ] + 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 - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "date", "value" => nil} - } - ] + 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) @@ -261,57 +320,60 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do 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 - } do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "date", "value" => ~D[2020-01-01]} - } - ] + 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 - } do - custom_field_values = [ - %{ - "custom_field_id" => string_field.id, - "value" => %{"type" => "string", "value" => "test"} - }, - %{ - "custom_field_id" => integer_field.id, - "value" => %{"type" => "integer", "value" => 42} - }, - %{ - "custom_field_id" => boolean_field.id, - "value" => %{"type" => "boolean", "value" => true} - } - ] + 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 - } do - custom_field_values = [ - %{ - "custom_field_id" => string_field.id, - "value" => %{"type" => "string", "value" => "test"} - } - # Missing required_integer_field - ] + 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) @@ -320,19 +382,26 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do assert error_message(errors, :custom_field_values) =~ integer_field.name end - test "succeeds when optional custom field is missing", %{optional_field: field} do - attrs = Map.put(@valid_attrs, :custom_field_values, []) + 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} do - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "string", "value" => nil} - } - ] + 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) @@ -341,16 +410,12 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do end describe "update_member with required custom fields" do - test "fails when removing a required custom field value", %{ - required_string_field: field - } do - # Create member with required custom field - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "string", "value" => "test"} - } - ] + 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(%{ @@ -368,16 +433,12 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do assert error_message(errors, :custom_field_values) =~ field.name end - test "fails when setting required custom field value to empty", %{ - required_string_field: field - } do - # Create member with required custom field - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "string", "value" => "test"} - } - ] + 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(%{ @@ -387,13 +448,16 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do custom_field_values: custom_field_values }) - # Try to update with empty value - updated_custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "string", "value" => ""} - } - ] + # 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, %{ @@ -404,16 +468,12 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do assert error_message(errors, :custom_field_values) =~ field.name end - test "succeeds when updating required custom field to valid value", %{ - required_string_field: field - } do - # Create member with required custom field - custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "string", "value" => "old value"} - } - ] + 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(%{ @@ -423,13 +483,31 @@ defmodule Mv.Membership.MemberRequiredCustomFieldsTest do custom_field_values: custom_field_values }) - # Update with new valid value - updated_custom_field_values = [ - %{ - "custom_field_id" => field.id, - "value" => %{"type" => "string", "value" => "new value"} - } - ] + # 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, %{