feat: email uniqueness constraint between user and member
This commit is contained in:
parent
5a0a261cd6
commit
39afaf3999
5 changed files with 329 additions and 6 deletions
201
test/accounts/email_uniqueness_test.exs
Normal file
201
test/accounts/email_uniqueness_test.exs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
defmodule Mv.Accounts.EmailUniquenessTest do
|
||||
use Mv.DataCase, async: false
|
||||
|
||||
alias Mv.Accounts
|
||||
alias Mv.Membership
|
||||
|
||||
describe "Email uniqueness validation" do
|
||||
test "cannot create member with existing unlinked user email" do
|
||||
# Create a user with email
|
||||
{:ok, _user} =
|
||||
Accounts.create_user(%{
|
||||
email: "existing@example.com"
|
||||
})
|
||||
|
||||
# Try to create member with same email
|
||||
result =
|
||||
Membership.create_member(%{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email: "existing@example.com"
|
||||
})
|
||||
|
||||
assert {:error, %Ash.Error.Invalid{} = error} = result
|
||||
|
||||
assert error.errors
|
||||
|> Enum.any?(fn e ->
|
||||
e.field == :email and
|
||||
(String.contains?(e.message, "already") or String.contains?(e.message, "used"))
|
||||
end)
|
||||
end
|
||||
|
||||
test "cannot create user with existing unlinked member email" do
|
||||
# Create a member with email
|
||||
{:ok, _member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email: "existing@example.com"
|
||||
})
|
||||
|
||||
# Try to create user with same email
|
||||
result =
|
||||
Accounts.create_user(%{
|
||||
email: "existing@example.com"
|
||||
})
|
||||
|
||||
assert {:error, %Ash.Error.Invalid{} = error} = result
|
||||
|
||||
assert error.errors
|
||||
|> Enum.any?(fn e ->
|
||||
e.field == :email and
|
||||
(String.contains?(e.message, "already") or String.contains?(e.message, "used"))
|
||||
end)
|
||||
end
|
||||
|
||||
test "member email cannot be changed to an existing unlinked user email" do
|
||||
# Create a user with email
|
||||
{:ok, user} =
|
||||
Accounts.create_user(%{
|
||||
email: "existing_user@example.com"
|
||||
})
|
||||
|
||||
# Create a member with different email
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email: "member@example.com"
|
||||
})
|
||||
|
||||
# Try to change member email to existing user email
|
||||
result =
|
||||
Membership.update_member(member, %{
|
||||
email: "existing_user@example.com"
|
||||
})
|
||||
|
||||
assert {:error, %Ash.Error.Invalid{} = error} = result
|
||||
|
||||
assert error.errors
|
||||
|> Enum.any?(fn e ->
|
||||
e.field == :email and
|
||||
(String.contains?(e.message, "already") or String.contains?(e.message, "used"))
|
||||
end)
|
||||
end
|
||||
|
||||
test "user email cannot be changed to an existing unlinked member email" do
|
||||
# Create a member with email
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email: "existing_member@example.com"
|
||||
})
|
||||
|
||||
# Create a user with different email
|
||||
{:ok, user} =
|
||||
Accounts.create_user(%{
|
||||
email: "user@example.com"
|
||||
})
|
||||
|
||||
# Try to change user email to existing member email
|
||||
result =
|
||||
Accounts.update_user(user, %{
|
||||
email: "existing_member@example.com"
|
||||
})
|
||||
|
||||
assert {:error, %Ash.Error.Invalid{} = error} = result
|
||||
|
||||
assert error.errors
|
||||
|> Enum.any?(fn e ->
|
||||
e.field == :email and
|
||||
(String.contains?(e.message, "already") or String.contains?(e.message, "used"))
|
||||
end)
|
||||
end
|
||||
|
||||
test "member email syncs to linked user email without validation error" do
|
||||
# Create a user
|
||||
{:ok, user} =
|
||||
Accounts.create_user(%{
|
||||
email: "user@example.com"
|
||||
})
|
||||
|
||||
# Create a member linked to this user
|
||||
# The override change will set member.email = user.email automatically
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email: "member@example.com",
|
||||
user: %{id: user.id}
|
||||
})
|
||||
|
||||
# Member email should have been overridden to user email
|
||||
# This happens through our sync mechanism, which should NOT trigger
|
||||
# the "email already used" validation because it's the same user
|
||||
{:ok, member_after_link} = Ash.get(Mv.Membership.Member, member.id)
|
||||
assert member_after_link.email == "user@example.com"
|
||||
end
|
||||
|
||||
test "user email syncs to linked member without validation error" do
|
||||
# Create a member
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email: "member@example.com"
|
||||
})
|
||||
|
||||
# Create a user linked to this member
|
||||
# The override change will set member.email = user.email automatically
|
||||
{:ok, user} =
|
||||
Accounts.create_user(%{
|
||||
email: "user@example.com",
|
||||
member: %{id: member.id}
|
||||
})
|
||||
|
||||
# Member email should have been overridden to user email
|
||||
# This happens through our sync mechanism, which should NOT trigger
|
||||
# the "email already used" validation because it's the same member
|
||||
{:ok, member_after_link} = Ash.get(Mv.Membership.Member, member.id)
|
||||
assert member_after_link.email == "user@example.com"
|
||||
end
|
||||
|
||||
test "two unlinked users cannot have the same email" do
|
||||
# Create first user
|
||||
{:ok, _user1} =
|
||||
Accounts.create_user(%{
|
||||
email: "duplicate@example.com"
|
||||
})
|
||||
|
||||
# Try to create second user with same email
|
||||
result =
|
||||
Accounts.create_user(%{
|
||||
email: "duplicate@example.com"
|
||||
})
|
||||
|
||||
assert {:error, %Ash.Error.Invalid{}} = result
|
||||
end
|
||||
|
||||
test "two unlinked members cannot have the same email (members have unique constraint)" do
|
||||
# Create first member
|
||||
{:ok, _member1} =
|
||||
Membership.create_member(%{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email: "duplicate@example.com"
|
||||
})
|
||||
|
||||
# Try to create second member with same email - should fail
|
||||
result =
|
||||
Membership.create_member(%{
|
||||
first_name: "Jane",
|
||||
last_name: "Smith",
|
||||
email: "duplicate@example.com"
|
||||
})
|
||||
|
||||
assert {:error, %Ash.Error.Invalid{}} = result
|
||||
# Members DO have a unique email constraint at database level
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue