defmodule Mv.Accounts.UserMemberLinkingTest do @moduledoc """ Integration tests for User-Member linking functionality. Tests the complete workflow of linking and unlinking members to users, including email synchronization and validation rules. """ use Mv.DataCase, async: false alias Mv.Accounts alias Mv.Membership describe "User-Member Linking with Email Sync" do test "link user to member with different email syncs member email" do # Create user with one email {:ok, user} = Accounts.create_user(%{email: "user@example.com"}) # Create member with different email {:ok, member} = Membership.create_member(%{ first_name: "John", last_name: "Doe", email: "member@example.com" }) # Link user to member {:ok, updated_user} = Accounts.update_user(user, %{member: %{id: member.id}}) # Verify link exists user_with_member = Ash.get!(Mv.Accounts.User, updated_user.id, load: [:member]) assert user_with_member.member.id == member.id # Verify member email was synced to match user email synced_member = Ash.get!(Mv.Membership.Member, member.id) assert synced_member.email == "user@example.com" end test "unlink member from user sets member to nil" do # Create and link user and member {:ok, user} = Accounts.create_user(%{email: "user@example.com"}) {:ok, member} = Membership.create_member(%{ first_name: "Jane", last_name: "Smith", email: "jane@example.com" }) {:ok, linked_user} = Accounts.update_user(user, %{member: %{id: member.id}}) # Verify link exists user_with_member = Ash.get!(Mv.Accounts.User, linked_user.id, load: [:member]) assert user_with_member.member.id == member.id # Unlink by setting member to nil {:ok, unlinked_user} = Accounts.update_user(linked_user, %{member: nil}) # Verify link is removed user_without_member = Ash.get!(Mv.Accounts.User, unlinked_user.id, load: [:member]) assert is_nil(user_without_member.member) # Verify member still exists independently member_still_exists = Ash.get!(Mv.Membership.Member, member.id) assert member_still_exists.id == member.id end test "cannot link member already linked to another user" do # Create first user and link to member {:ok, user1} = Accounts.create_user(%{email: "user1@example.com"}) {:ok, member} = Membership.create_member(%{ first_name: "Bob", last_name: "Wilson", email: "bob@example.com" }) {:ok, _linked_user1} = Accounts.update_user(user1, %{member: %{id: member.id}}) # Create second user and try to link to same member {:ok, user2} = Accounts.create_user(%{email: "user2@example.com"}) # Should fail because member is already linked assert {:error, %Ash.Error.Invalid{}} = Accounts.update_user(user2, %{member: %{id: member.id}}) end test "cannot change member link directly, must unlink first" do # Create user and link to first member {:ok, user} = Accounts.create_user(%{email: "user@example.com"}) {:ok, member1} = Membership.create_member(%{ first_name: "Alice", last_name: "Johnson", email: "alice@example.com" }) {:ok, linked_user} = Accounts.update_user(user, %{member: %{id: member1.id}}) # Create second member {:ok, member2} = Membership.create_member(%{ first_name: "Charlie", last_name: "Brown", email: "charlie@example.com" }) # Try to directly change member link (should fail) assert {:error, %Ash.Error.Invalid{errors: errors}} = Accounts.update_user(linked_user, %{member: %{id: member2.id}}) # Verify error message mentions "Remove existing member first" error_messages = Enum.map(errors, & &1.message) assert Enum.any?(error_messages, &String.contains?(&1, "Remove existing member first")) # Two-step process: first unlink, then link new member {:ok, unlinked_user} = Accounts.update_user(linked_user, %{member: nil}) # After unlinking, member1 still has the user's email # Change member1's email to avoid conflict when relinking to member2 {:ok, _} = Membership.update_member(member1, %{email: "alice_changed@example.com"}) {:ok, relinked_user} = Accounts.update_user(unlinked_user, %{member: %{id: member2.id}}) # Verify new link is established user_with_new_member = Ash.get!(Mv.Accounts.User, relinked_user.id, load: [:member]) assert user_with_new_member.member.id == member2.id end end end