defmodule Mv.Membership.MemberGroupTest do @moduledoc """ Tests for MemberGroup join table resource - validations and cascade delete behavior. """ use Mv.DataCase, async: true alias Mv.Membership require Ash.Query import Ash.Expr setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() %{actor: system_actor} end describe "Validations & Associations" do test "create association between member and group", %{actor: actor} do {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor) {:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor) assert {:ok, member_group} = Membership.create_member_group(%{member_id: member.id, group_id: group.id}, actor: actor ) assert member_group.member_id == member.id assert member_group.group_id == group.id end test "prevent duplicate associations (same member + same group)", %{actor: actor} do {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor) {:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor) {:ok, _mg1} = Membership.create_member_group(%{member_id: member.id, group_id: group.id}, actor: actor ) # Try to create duplicate assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member_group(%{member_id: member.id, group_id: group.id}, actor: actor ) assert Enum.any?(errors, fn err -> field = Map.get(err, :field) message = Map.get(err, :message, "") (field == :member_id or field == :group_id) and (String.contains?(message, "already been taken") or String.contains?(message, "already exists") or String.contains?(message, "duplicate") or String.contains?(message, "already in this group")) end) end end describe "Cascade Delete Behavior" do test "cascade delete when member deleted (MemberGroup deleted, Group remains)", %{ actor: actor } do {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor) {:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor) {:ok, _mg} = Membership.create_member_group(%{member_id: member.id, group_id: group.id}, actor: actor ) # Delete member :ok = Membership.destroy_member(member, actor: actor) # Group should still exist {:ok, group_reloaded} = Ash.get(Mv.Membership.Group, group.id, actor: actor) assert group_reloaded != nil # MemberGroup should be deleted {:ok, mgs} = Ash.read( Mv.Membership.MemberGroup |> Ash.Query.filter(expr(member_id == ^member.id)), actor: actor, domain: Mv.Membership ) assert mgs == [] end test "cascade delete when group deleted (MemberGroup deleted, Member remains)", %{ actor: actor } do {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor) {:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor) {:ok, _mg} = Membership.create_member_group(%{member_id: member.id, group_id: group.id}, actor: actor ) # Delete group :ok = Membership.destroy_group(group, actor: actor) # Member should still exist {:ok, member_reloaded} = Ash.get(Mv.Membership.Member, member.id, actor: actor) assert member_reloaded != nil # MemberGroup should be deleted {:ok, mgs} = Ash.read( Mv.Membership.MemberGroup |> Ash.Query.filter(expr(group_id == ^group.id)), actor: actor, domain: Mv.Membership ) assert mgs == [] end end end