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 describe "CASCADE behavior" do test "deleting member deletes associated membership_fee_cycles" do # Create member {:ok, member} = Ash.create(Member, %{ first_name: "Cascade", last_name: "Test", email: "cascade.test.#{System.unique_integer([:positive])}@example.com" }) # Create fee type {:ok, fee_type} = Ash.create(MembershipFeeType, %{ name: "Cascade Test Fee #{System.unique_integer([:positive])}", amount: Decimal.new("100.00"), interval: :monthly }) # 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 }) {: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 }) # Verify cycles exist assert {:ok, _} = Ash.get(MembershipFeeCycle, cycle1.id) assert {:ok, _} = Ash.get(MembershipFeeCycle, cycle2.id) # Delete member assert :ok = Ash.destroy(member) # Verify cycles are also deleted (CASCADE) # NotFound is wrapped in Ash.Error.Invalid assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeCycle, cycle1.id) assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeCycle, cycle2.id) end end describe "RESTRICT behavior" do test "cannot delete membership_fee_type if cycles reference it" do # Create member {:ok, member} = Ash.create(Member, %{ first_name: "Restrict", last_name: "Test", email: "restrict.test.#{System.unique_integer([:positive])}@example.com" }) # Create fee type {:ok, fee_type} = Ash.create(MembershipFeeType, %{ name: "Restrict Test Fee #{System.unique_integer([:positive])}", amount: Decimal.new("100.00"), interval: :monthly }) # 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 }) # Try to delete fee type - should fail due to RESTRICT assert {:error, error} = Ash.destroy(fee_type) # 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" 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 }) # Should be able to delete assert :ok = Ash.destroy(fee_type) # Verify it's gone (NotFound is wrapped in Ash.Error.Invalid) assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeType, fee_type.id) end test "cannot delete membership_fee_type if members reference it" 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 }) # 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 }) # Try to delete fee type - should fail due to RESTRICT assert {:error, error} = Ash.destroy(fee_type) 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" 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 }) # 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 }) assert member.membership_fee_type_id == fee_type.id end test "member can be created with membership_fee_start_date" 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] }) assert member.membership_fee_start_date == ~D[2025-01-01] end test "member can be created without membership fee fields" do {:ok, member} = Ash.create(Member, %{ first_name: "No", last_name: "FeeFields", email: "no.feefields.#{System.unique_integer([:positive])}@example.com" }) 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" 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 }) # 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" }) 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}) assert updated_member.membership_fee_type_id == fee_type.id end test "member can be updated with membership_fee_start_date" do {:ok, member} = Ash.create(Member, %{ first_name: "Start", last_name: "Date", email: "start.date.#{System.unique_integer([:positive])}@example.com" }) assert member.membership_fee_start_date == nil {:ok, updated_member} = Ash.update(member, %{membership_fee_start_date: ~D[2025-06-01]}) assert updated_member.membership_fee_start_date == ~D[2025-06-01] end end end