defmodule Mv.Membership.GroupIntegrationTest do @moduledoc """ Integration tests for many-to-many relationships and query performance. """ use Mv.DataCase, async: false alias Mv.Membership require Ash.Query setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() %{actor: system_actor} end describe "Many-to-Many Relationship" do test "member can belong to multiple groups", %{actor: actor} do {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor) {:ok, group1} = Membership.create_group(%{name: "Group One"}, actor: actor) {:ok, group2} = Membership.create_group(%{name: "Group Two"}, actor: actor) {:ok, group3} = Membership.create_group(%{name: "Group Three"}, actor: actor) # Add member to all groups {:ok, _mg1} = Membership.create_member_group(%{member_id: member.id, group_id: group1.id}, actor: actor ) {:ok, _mg2} = Membership.create_member_group(%{member_id: member.id, group_id: group2.id}, actor: actor ) {:ok, _mg3} = Membership.create_member_group(%{member_id: member.id, group_id: group3.id}, actor: actor ) # Load member with groups {:ok, member_with_groups} = Ash.load(member, :groups, actor: actor, domain: Mv.Membership) assert length(member_with_groups.groups) == 3 assert Enum.any?(member_with_groups.groups, &(&1.id == group1.id)) assert Enum.any?(member_with_groups.groups, &(&1.id == group2.id)) assert Enum.any?(member_with_groups.groups, &(&1.id == group3.id)) end test "group can contain multiple members", %{actor: actor} do {:ok, member1} = Membership.create_member(%{email: "member1@test.com"}, actor: actor) {:ok, member2} = Membership.create_member(%{email: "member2@test.com"}, actor: actor) {:ok, member3} = Membership.create_member(%{email: "member3@test.com"}, actor: actor) {:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor) # Add all members to group {:ok, _mg1} = Membership.create_member_group(%{member_id: member1.id, group_id: group.id}, actor: actor ) {:ok, _mg2} = Membership.create_member_group(%{member_id: member2.id, group_id: group.id}, actor: actor ) {:ok, _mg3} = Membership.create_member_group(%{member_id: member3.id, group_id: group.id}, actor: actor ) # Load group with members {:ok, group_with_members} = Ash.load(group, :members, actor: actor, domain: Mv.Membership) assert length(group_with_members.members) == 3 assert Enum.any?(group_with_members.members, &(&1.id == member1.id)) assert Enum.any?(group_with_members.members, &(&1.id == member2.id)) assert Enum.any?(group_with_members.members, &(&1.id == member3.id)) end end @moduletag :slow describe "Query Performance" do test "preloading groups with members avoids N+1 queries", %{actor: actor} do # Create test data {:ok, member1} = Membership.create_member(%{email: "member1@test.com"}, actor: actor) {:ok, member2} = Membership.create_member(%{email: "member2@test.com"}, actor: actor) {:ok, group1} = Membership.create_group(%{name: "Group One"}, actor: actor) {:ok, group2} = Membership.create_group(%{name: "Group Two"}, actor: actor) # Create associations {:ok, _mg1} = Membership.create_member_group(%{member_id: member1.id, group_id: group1.id}, actor: actor ) {:ok, _mg2} = Membership.create_member_group(%{member_id: member1.id, group_id: group2.id}, actor: actor ) {:ok, _mg3} = Membership.create_member_group(%{member_id: member2.id, group_id: group1.id}, actor: actor ) # Count queries using Telemetry query_count = Agent.start_link(fn -> 0 end) |> elem(1) handler = fn _event, _measurements, _metadata, _config -> Agent.update(query_count, &(&1 + 1)) end :telemetry.attach("test-query-counter", [:ash, :query, :start], handler, nil) # Load all members with groups preloaded (should be efficient with JOIN) {:ok, members} = Ash.read(Mv.Membership.Member, actor: actor, domain: Mv.Membership, load: [:groups]) final_count = Agent.get(query_count, & &1) :telemetry.detach("test-query-counter") member1_loaded = Enum.find(members, &(&1.id == member1.id)) member2_loaded = Enum.find(members, &(&1.id == member2.id)) # Verify preloading worked assert length(member1_loaded.groups) == 2 assert length(member2_loaded.groups) == 1 # Verify groups are correctly associated assert Enum.any?(member1_loaded.groups, &(&1.id == group1.id)) assert Enum.any?(member1_loaded.groups, &(&1.id == group2.id)) assert Enum.any?(member2_loaded.groups, &(&1.id == group1.id)) # Verify query count is reasonable (should be 2 queries: one for members, one for groups) # Note: Exact count may vary based on Ash implementation, but should be much less than N+1 assert final_count <= 3, "Expected max 3 queries (members + groups + possible count), got #{final_count}. This suggests N+1 query problem." end end end