defmodule Mv.MembershipFees.ForeignKeyTest do @moduledoc """ Tests for foreign key behaviors (CASCADE and RESTRICT). """ use Mv.DataCase, async: true alias Mv.MembershipFees.MembershipFeeCycle alias Mv.MembershipFees.MembershipFeeType alias Mv.Membership.Member setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() %{actor: system_actor} end describe "CASCADE behavior" do test "deleting member deletes associated membership_fee_cycles", %{actor: actor} do # Create member {:ok, member} = Ash.create( Member, %{ first_name: "Cascade", last_name: "Test", email: "cascade.test.#{System.unique_integer([:positive])}@example.com" }, actor: actor ) # Create fee type {:ok, fee_type} = Ash.create( MembershipFeeType, %{ name: "Cascade Test Fee #{System.unique_integer([:positive])}", amount: Decimal.new("100.00"), interval: :monthly }, actor: actor ) # Create multiple cycles for this member {:ok, cycle1} = Ash.create( MembershipFeeCycle, %{ cycle_start: ~D[2025-01-01], amount: Decimal.new("100.00"), member_id: member.id, membership_fee_type_id: fee_type.id }, actor: actor ) {:ok, cycle2} = Ash.create( MembershipFeeCycle, %{ cycle_start: ~D[2025-02-01], amount: Decimal.new("100.00"), member_id: member.id, membership_fee_type_id: fee_type.id }, actor: actor ) # Verify cycles exist assert {:ok, _} = Ash.get(MembershipFeeCycle, cycle1.id, actor: actor) assert {:ok, _} = Ash.get(MembershipFeeCycle, cycle2.id, actor: actor) # Delete member assert :ok = Ash.destroy(member, actor: actor) # Verify cycles are also deleted (CASCADE) # NotFound is wrapped in Ash.Error.Invalid assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeCycle, cycle1.id, actor: actor) assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeCycle, cycle2.id, actor: actor) end end describe "RESTRICT behavior" do test "cannot delete membership_fee_type if cycles reference it", %{actor: actor} do # Create member {:ok, member} = Ash.create( Member, %{ first_name: "Restrict", last_name: "Test", email: "restrict.test.#{System.unique_integer([:positive])}@example.com" }, actor: actor ) # Create fee type {:ok, fee_type} = Ash.create( MembershipFeeType, %{ name: "Restrict Test Fee #{System.unique_integer([:positive])}", amount: Decimal.new("100.00"), interval: :monthly }, actor: actor ) # Create a cycle referencing this fee type {:ok, _cycle} = Ash.create( MembershipFeeCycle, %{ cycle_start: ~D[2025-01-01], amount: Decimal.new("100.00"), member_id: member.id, membership_fee_type_id: fee_type.id }, actor: actor ) # Try to delete fee type - should fail due to RESTRICT assert {:error, error} = Ash.destroy(fee_type, actor: actor) # Check that it's a foreign key violation error assert is_struct(error, Ash.Error.Invalid) or is_struct(error, Ash.Error.Unknown) end test "can delete membership_fee_type if no cycles reference it", %{actor: actor} do # Create fee type without any cycles {:ok, fee_type} = Ash.create( MembershipFeeType, %{ name: "Deletable Fee #{System.unique_integer([:positive])}", amount: Decimal.new("100.00"), interval: :monthly }, actor: actor ) # Should be able to delete assert :ok = Ash.destroy(fee_type, actor: actor) # Verify it's gone (NotFound is wrapped in Ash.Error.Invalid) assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeType, fee_type.id, actor: actor) end test "cannot delete membership_fee_type if members reference it", %{actor: actor} do # Create fee type {:ok, fee_type} = Ash.create( MembershipFeeType, %{ name: "Member Ref Fee #{System.unique_integer([:positive])}", amount: Decimal.new("100.00"), interval: :monthly }, actor: actor ) # Create member with this fee type {:ok, _member} = Ash.create( Member, %{ first_name: "FeeType", last_name: "Reference", email: "feetype.ref.#{System.unique_integer([:positive])}@example.com", membership_fee_type_id: fee_type.id }, actor: actor ) # Try to delete fee type - should fail due to RESTRICT assert {:error, error} = Ash.destroy(fee_type, actor: actor) assert is_struct(error, Ash.Error.Invalid) or is_struct(error, Ash.Error.Unknown) end end describe "member extensions" do test "member can be created with membership_fee_type_id", %{actor: actor} do # Create fee type first {:ok, fee_type} = Ash.create( MembershipFeeType, %{ name: "Create Test Fee #{System.unique_integer([:positive])}", amount: Decimal.new("100.00"), interval: :yearly }, actor: actor ) # Create member with fee type {:ok, member} = Ash.create( Member, %{ first_name: "With", last_name: "FeeType", email: "with.feetype.#{System.unique_integer([:positive])}@example.com", membership_fee_type_id: fee_type.id }, actor: actor ) assert member.membership_fee_type_id == fee_type.id end test "member can be created with membership_fee_start_date", %{actor: actor} do {:ok, member} = Ash.create( Member, %{ first_name: "With", last_name: "StartDate", email: "with.startdate.#{System.unique_integer([:positive])}@example.com", membership_fee_start_date: ~D[2025-01-01] }, actor: actor ) assert member.membership_fee_start_date == ~D[2025-01-01] end test "member can be created without membership fee fields", %{actor: actor} do {:ok, member} = Ash.create( Member, %{ first_name: "No", last_name: "FeeFields", email: "no.feefields.#{System.unique_integer([:positive])}@example.com" }, actor: actor ) assert member.membership_fee_type_id == nil assert member.membership_fee_start_date == nil end test "member can be updated with membership_fee_type_id", %{actor: actor} do # Create fee type {:ok, fee_type} = Ash.create( MembershipFeeType, %{ name: "Update Test Fee #{System.unique_integer([:positive])}", amount: Decimal.new("100.00"), interval: :yearly }, actor: actor ) # Create member without fee type {:ok, member} = Ash.create( Member, %{ first_name: "Update", last_name: "Test", email: "update.test.#{System.unique_integer([:positive])}@example.com" }, actor: actor ) assert member.membership_fee_type_id == nil # Update member with fee type {:ok, updated_member} = Ash.update(member, %{membership_fee_type_id: fee_type.id}, actor: actor) assert updated_member.membership_fee_type_id == fee_type.id end test "member can be updated with membership_fee_start_date", %{actor: actor} do {:ok, member} = Ash.create( Member, %{ first_name: "Start", last_name: "Date", email: "start.date.#{System.unique_integer([:positive])}@example.com" }, actor: actor ) assert member.membership_fee_start_date == nil {:ok, updated_member} = Ash.update(member, %{membership_fee_start_date: ~D[2025-06-01]}, actor: actor) assert updated_member.membership_fee_start_date == ~D[2025-06-01] end end end