defmodule Mv.Accounts.EmailSyncEdgeCasesTest do @moduledoc """ Edge case tests for email synchronization between User and Member. Tests various boundary conditions and validation scenarios. """ use Mv.DataCase, async: false alias Mv.Accounts alias Mv.Membership setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() %{actor: system_actor} end describe "Email sync edge cases" do @valid_user_attrs %{ email: "user@example.com" } @valid_member_attrs %{ first_name: "John", last_name: "Doe", email: "member@example.com" } test "simultaneous email updates use user email as source of truth", %{actor: actor} do # Create linked user and member {:ok, member} = Membership.create_member(@valid_member_attrs, actor: actor) {:ok, user} = Accounts.create_user(Map.put(@valid_user_attrs, :member, %{id: member.id}), actor: actor) # Verify link and initial sync {:ok, synced_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor) assert synced_member.email == "user@example.com" # Scenario: Both emails are updated "simultaneously" # In practice, this tests that when a member email is updated, # it syncs to user, and user remains the source of truth # Update member email first {:ok, _updated_member} = Membership.update_member(member, %{email: "member-new@example.com"}, actor: actor) # Verify it synced to user {:ok, user_after_member_update} = Ash.get(Mv.Accounts.User, user.id, actor: actor) assert to_string(user_after_member_update.email) == "member-new@example.com" # Now update user email - this should override {:ok, _updated_user} = Accounts.update_user(user_after_member_update, %{email: "user-final@example.com"}, actor: actor ) # Reload both {:ok, final_user} = Ash.get(Mv.Accounts.User, user.id, actor: actor) {:ok, final_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor) # User email should be the final truth assert to_string(final_user.email) == "user-final@example.com" assert final_member.email == "user-final@example.com" end test "email validation works for both user and member", %{actor: actor} do # Test that invalid emails are rejected for both resources # Invalid email for user invalid_user_result = Accounts.create_user(%{email: "not-an-email"}, actor: actor) assert {:error, %Ash.Error.Invalid{}} = invalid_user_result # Invalid email for member invalid_member_attrs = Map.put(@valid_member_attrs, :email, "also-not-an-email") invalid_member_result = Membership.create_member(invalid_member_attrs, actor: actor) assert {:error, %Ash.Error.Invalid{}} = invalid_member_result # Valid emails should work {:ok, _user} = Accounts.create_user(@valid_user_attrs, actor: actor) {:ok, _member} = Membership.create_member(@valid_member_attrs, actor: actor) end test "identity constraints prevent duplicate emails", %{actor: actor} do # Create first user with an email {:ok, user1} = Accounts.create_user(%{email: "duplicate@example.com"}, actor: actor) assert to_string(user1.email) == "duplicate@example.com" # Try to create second user with same email - should fail due to unique constraint result = Accounts.create_user(%{email: "duplicate@example.com"}, actor: actor) assert {:error, %Ash.Error.Invalid{}} = result # Same for members member_attrs = Map.put(@valid_member_attrs, :email, "member-dup@example.com") {:ok, member1} = Membership.create_member(member_attrs, actor: actor) assert member1.email == "member-dup@example.com" # Try to create second member with same email - should fail result2 = Membership.create_member(member_attrs, actor: actor) assert {:error, %Ash.Error.Invalid{}} = result2 end end end