222 lines
7 KiB
Elixir
222 lines
7 KiB
Elixir
defmodule Mv.Membership.MemberAvailableForLinkingTest do
|
|
@moduledoc """
|
|
Tests for the Member.available_for_linking action.
|
|
|
|
This action returns members that can be linked to a user account:
|
|
- Only members without existing user links (user_id == nil)
|
|
- Limited to 10 results
|
|
- Special email-match logic: if user_email matches member email, only return that member
|
|
- Optional search query filtering by name and email
|
|
"""
|
|
use Mv.DataCase, async: true
|
|
alias Mv.Membership
|
|
|
|
describe "available_for_linking/2" do
|
|
setup do
|
|
# Create 5 unlinked members with distinct names
|
|
{:ok, member1} =
|
|
Membership.create_member(%{
|
|
first_name: "Alice",
|
|
last_name: "Anderson",
|
|
email: "alice@example.com"
|
|
})
|
|
|
|
{:ok, member2} =
|
|
Membership.create_member(%{
|
|
first_name: "Bob",
|
|
last_name: "Williams",
|
|
email: "bob@example.com"
|
|
})
|
|
|
|
{:ok, member3} =
|
|
Membership.create_member(%{
|
|
first_name: "Charlie",
|
|
last_name: "Davis",
|
|
email: "charlie@example.com"
|
|
})
|
|
|
|
{:ok, member4} =
|
|
Membership.create_member(%{
|
|
first_name: "Diana",
|
|
last_name: "Martinez",
|
|
email: "diana@example.com"
|
|
})
|
|
|
|
{:ok, member5} =
|
|
Membership.create_member(%{
|
|
first_name: "Emma",
|
|
last_name: "Taylor",
|
|
email: "emma@example.com"
|
|
})
|
|
|
|
unlinked_members = [member1, member2, member3, member4, member5]
|
|
|
|
# Create 2 linked members (with users)
|
|
{:ok, user1} = Mv.Accounts.create_user(%{email: "user1@example.com"})
|
|
|
|
{:ok, linked_member1} =
|
|
Membership.create_member(%{
|
|
first_name: "Linked",
|
|
last_name: "Member1",
|
|
email: "linked1@example.com",
|
|
user: %{id: user1.id}
|
|
})
|
|
|
|
{:ok, user2} = Mv.Accounts.create_user(%{email: "user2@example.com"})
|
|
|
|
{:ok, linked_member2} =
|
|
Membership.create_member(%{
|
|
first_name: "Linked",
|
|
last_name: "Member2",
|
|
email: "linked2@example.com",
|
|
user: %{id: user2.id}
|
|
})
|
|
|
|
%{
|
|
unlinked_members: unlinked_members,
|
|
linked_members: [linked_member1, linked_member2]
|
|
}
|
|
end
|
|
|
|
test "returns only unlinked members and limits to 10", %{
|
|
unlinked_members: unlinked_members,
|
|
linked_members: _linked_members
|
|
} do
|
|
# Call the action without any arguments
|
|
members =
|
|
Mv.Membership.Member
|
|
|> Ash.Query.for_read(:available_for_linking, %{})
|
|
|> Ash.read!()
|
|
|
|
# Should return only the 5 unlinked members, not the 2 linked ones
|
|
assert length(members) == 5
|
|
|
|
returned_ids = Enum.map(members, & &1.id) |> MapSet.new()
|
|
expected_ids = Enum.map(unlinked_members, & &1.id) |> MapSet.new()
|
|
|
|
assert MapSet.equal?(returned_ids, expected_ids)
|
|
|
|
# Verify none of the returned members have a user_id
|
|
Enum.each(members, fn member ->
|
|
member_with_user = Ash.get!(Mv.Membership.Member, member.id, load: [:user])
|
|
assert is_nil(member_with_user.user)
|
|
end)
|
|
end
|
|
|
|
test "limits results to 10 members even when more exist" do
|
|
# Create 15 additional unlinked members (total 20 unlinked)
|
|
for i <- 6..20 do
|
|
Membership.create_member(%{
|
|
first_name: "Extra#{i}",
|
|
last_name: "Member#{i}",
|
|
email: "extra#{i}@example.com"
|
|
})
|
|
end
|
|
|
|
members =
|
|
Mv.Membership.Member
|
|
|> Ash.Query.for_read(:available_for_linking, %{})
|
|
|> Ash.read!()
|
|
|
|
# Should be limited to 10
|
|
assert length(members) == 10
|
|
end
|
|
|
|
test "email match: returns only member with matching email when exists", %{
|
|
unlinked_members: unlinked_members
|
|
} do
|
|
# Get one of the unlinked members' email
|
|
target_member = List.first(unlinked_members)
|
|
user_email = target_member.email
|
|
|
|
raw_members =
|
|
Mv.Membership.Member
|
|
|> Ash.Query.for_read(:available_for_linking, %{user_email: user_email})
|
|
|> Ash.read!()
|
|
|
|
# Apply email match filtering (sorted results come from query)
|
|
# When user_email matches, only that member should be returned
|
|
members = Mv.Membership.Member.filter_by_email_match(raw_members, user_email)
|
|
|
|
# Should return only the member with matching email
|
|
assert length(members) == 1
|
|
assert List.first(members).id == target_member.id
|
|
assert List.first(members).email == user_email
|
|
end
|
|
|
|
test "email match: returns all unlinked members when no email match" do
|
|
# Use an email that doesn't match any member
|
|
non_matching_email = "nonexistent@example.com"
|
|
|
|
raw_members =
|
|
Mv.Membership.Member
|
|
|> Ash.Query.for_read(:available_for_linking, %{user_email: non_matching_email})
|
|
|> Ash.read!()
|
|
|
|
# Apply email match filtering
|
|
members = Mv.Membership.Member.filter_by_email_match(raw_members, non_matching_email)
|
|
|
|
# Should return all 5 unlinked members since no match
|
|
assert length(members) == 5
|
|
end
|
|
|
|
test "search query: filters by first_name, last_name, and email", %{
|
|
unlinked_members: _unlinked_members
|
|
} do
|
|
# Search by first name
|
|
members =
|
|
Mv.Membership.Member
|
|
|> Ash.Query.for_read(:available_for_linking, %{search_query: "Alice"})
|
|
|> Ash.read!()
|
|
|
|
assert length(members) == 1
|
|
assert List.first(members).first_name == "Alice"
|
|
|
|
# Search by last name
|
|
members =
|
|
Mv.Membership.Member
|
|
|> Ash.Query.for_read(:available_for_linking, %{search_query: "Williams"})
|
|
|> Ash.read!()
|
|
|
|
assert length(members) == 1
|
|
assert List.first(members).last_name == "Williams"
|
|
|
|
# Search by email
|
|
members =
|
|
Mv.Membership.Member
|
|
|> Ash.Query.for_read(:available_for_linking, %{search_query: "charlie@"})
|
|
|> Ash.read!()
|
|
|
|
assert length(members) == 1
|
|
assert List.first(members).email == "charlie@example.com"
|
|
|
|
# Search returns empty when no matches
|
|
members =
|
|
Mv.Membership.Member
|
|
|> Ash.Query.for_read(:available_for_linking, %{search_query: "NonExistent"})
|
|
|> Ash.read!()
|
|
|
|
assert Enum.empty?(members)
|
|
end
|
|
|
|
test "search query takes precedence over email match", %{unlinked_members: unlinked_members} do
|
|
target_member = List.first(unlinked_members)
|
|
|
|
# Pass both email match and search query that would match different members
|
|
raw_members =
|
|
Mv.Membership.Member
|
|
|> Ash.Query.for_read(:available_for_linking, %{
|
|
user_email: target_member.email,
|
|
search_query: "Bob"
|
|
})
|
|
|> Ash.read!()
|
|
|
|
# Search query takes precedence, should match "Bob" in the first name
|
|
# user_email is used for POST-filtering only, not in the query
|
|
assert length(raw_members) == 1
|
|
# Should find the member with "Bob" first name, not target_member (Alice)
|
|
assert List.first(raw_members).first_name == "Bob"
|
|
refute List.first(raw_members).id == target_member.id
|
|
end
|
|
end
|
|
end
|