defmodule Mv.Membership.CustomFieldDeletionTest do @moduledoc """ Tests for CustomField deletion with CASCADE behavior. Tests cover: - Deletion of custom fields without assigned values - Deletion of custom fields with assigned values (CASCADE) - assigned_members_count calculation - prepare_deletion action with count loading - CASCADE deletion only affects specific custom field values """ use Mv.DataCase, async: true alias Mv.Membership.{CustomField, CustomFieldValue, Member} setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() %{actor: system_actor} end describe "assigned_members_count calculation" do test "returns 0 for custom field without any values", %{actor: actor} do {:ok, custom_field} = CustomField |> Ash.Changeset.for_create(:create, %{ name: "test_field", value_type: :string }) |> Ash.create(actor: actor) custom_field_with_count = Ash.load!(custom_field, :assigned_members_count, actor: actor) assert custom_field_with_count.assigned_members_count == 0 end test "returns correct count for custom field with one member", %{actor: actor} do {:ok, member} = create_member(actor) {:ok, custom_field} = create_custom_field("test_field", :string, actor) {:ok, _custom_field_value} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member.id, custom_field_id: custom_field.id, value: %{"_union_type" => "string", "_union_value" => "test"} }) |> Ash.create(actor: actor) custom_field_with_count = Ash.load!(custom_field, :assigned_members_count, actor: actor) assert custom_field_with_count.assigned_members_count == 1 end test "returns correct count for custom field with multiple members", %{actor: actor} do {:ok, member1} = create_member(actor) {:ok, member2} = create_member(actor) {:ok, member3} = create_member(actor) {:ok, custom_field} = create_custom_field("test_field", :string, actor) # Create custom field value for each member for member <- [member1, member2, member3] do {:ok, _} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member.id, custom_field_id: custom_field.id, value: %{"_union_type" => "string", "_union_value" => "test"} }) |> Ash.create(actor: actor) end custom_field_with_count = Ash.load!(custom_field, :assigned_members_count, actor: actor) assert custom_field_with_count.assigned_members_count == 3 end test "counts distinct members (not multiple values per member)", %{actor: actor} do {:ok, member} = create_member(actor) {:ok, custom_field} = create_custom_field("test_field", :string, actor) # Create custom field value for member {:ok, _} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member.id, custom_field_id: custom_field.id, value: %{"_union_type" => "string", "_union_value" => "test"} }) |> Ash.create(actor: actor) custom_field_with_count = Ash.load!(custom_field, :assigned_members_count, actor: actor) # Should still be 1, not 2, even if we tried to create multiple (which would fail due to uniqueness) assert custom_field_with_count.assigned_members_count == 1 end end describe "prepare_deletion action" do test "loads assigned_members_count for deletion preparation", %{actor: actor} do {:ok, member} = create_member(actor) {:ok, custom_field} = create_custom_field("test_field", :string, actor) {:ok, _} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member.id, custom_field_id: custom_field.id, value: %{"_union_type" => "string", "_union_value" => "test"} }) |> Ash.create(actor: actor) # Use prepare_deletion action [prepared_custom_field] = CustomField |> Ash.Query.for_read(:prepare_deletion, %{id: custom_field.id}) |> Ash.read!(actor: actor) assert prepared_custom_field.assigned_members_count == 1 assert prepared_custom_field.id == custom_field.id end test "returns empty list for non-existent custom field", %{actor: actor} do non_existent_id = Ash.UUID.generate() result = CustomField |> Ash.Query.for_read(:prepare_deletion, %{id: non_existent_id}) |> Ash.read!(actor: actor) assert result == [] end end describe "destroy_with_values action" do test "deletes custom field without any values", %{actor: actor} do {:ok, custom_field} = create_custom_field("test_field", :string, actor) assert :ok = Ash.destroy(custom_field, actor: actor) # Verify custom field is deleted assert {:error, _} = Ash.get(CustomField, custom_field.id, actor: actor) end test "deletes custom field and cascades to all its values", %{actor: actor} do {:ok, member} = create_member(actor) {:ok, custom_field} = create_custom_field("test_field", :string, actor) {:ok, custom_field_value} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member.id, custom_field_id: custom_field.id, value: %{"_union_type" => "string", "_union_value" => "test"} }) |> Ash.create(actor: actor) # Delete custom field assert :ok = Ash.destroy(custom_field, actor: actor) # Verify custom field is deleted assert {:error, _} = Ash.get(CustomField, custom_field.id, actor: actor) # Verify custom field value is also deleted (CASCADE) assert {:error, _} = Ash.get(CustomFieldValue, custom_field_value.id, actor: actor) # Verify member still exists assert {:ok, _} = Ash.get(Member, member.id, actor: actor) end test "deletes only values of the specific custom field", %{actor: actor} do {:ok, member} = create_member(actor) {:ok, custom_field1} = create_custom_field("field1", :string, actor) {:ok, custom_field2} = create_custom_field("field2", :string, actor) # Create value for custom_field1 {:ok, value1} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member.id, custom_field_id: custom_field1.id, value: %{"_union_type" => "string", "_union_value" => "value1"} }) |> Ash.create(actor: actor) # Create value for custom_field2 {:ok, value2} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member.id, custom_field_id: custom_field2.id, value: %{"_union_type" => "string", "_union_value" => "value2"} }) |> Ash.create(actor: actor) # Delete custom_field1 assert :ok = Ash.destroy(custom_field1, actor: actor) # Verify custom_field1 and value1 are deleted assert {:error, _} = Ash.get(CustomField, custom_field1.id, actor: actor) assert {:error, _} = Ash.get(CustomFieldValue, value1.id, actor: actor) # Verify custom_field2 and value2 still exist assert {:ok, _} = Ash.get(CustomField, custom_field2.id, actor: actor) assert {:ok, _} = Ash.get(CustomFieldValue, value2.id, actor: actor) end test "deletes custom field with values from multiple members", %{actor: actor} do {:ok, member1} = create_member(actor) {:ok, member2} = create_member(actor) {:ok, member3} = create_member(actor) {:ok, custom_field} = create_custom_field("test_field", :string, actor) # Create value for each member values = for member <- [member1, member2, member3] do {:ok, value} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member.id, custom_field_id: custom_field.id, value: %{"_union_type" => "string", "_union_value" => "test"} }) |> Ash.create(actor: actor) value end # Delete custom field assert :ok = Ash.destroy(custom_field, actor: actor) # Verify all values are deleted for value <- values do assert {:error, _} = Ash.get(CustomFieldValue, value.id, actor: actor) end # Verify all members still exist for member <- [member1, member2, member3] do assert {:ok, _} = Ash.get(Member, member.id, actor: actor) end end end # Helper functions defp create_member(actor) do Mv.Membership.create_member( %{ first_name: "Test", last_name: "User#{System.unique_integer([:positive])}", email: "test#{System.unique_integer([:positive])}@example.com" }, actor: actor ) end defp create_custom_field(name, value_type, actor) do CustomField |> Ash.Changeset.for_create(:create, %{ name: "#{name}_#{System.unique_integer([:positive])}", value_type: value_type }) |> Ash.create(actor: actor) end end