defmodule Mv.Membership.MemberFuzzySearchLinkingTest do @moduledoc """ Tests fuzzy search in Member.available_for_linking action. Verifies PostgreSQL trigram matching for member search. """ 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 "available_for_linking with fuzzy search" do test "finds member despite typo", %{actor: actor} do # Create member with specific name {:ok, member} = Membership.create_member( %{ first_name: "Jonathan", last_name: "Smith", email: "jonathan@example.com" }, actor: actor ) # Search with typo query = Mv.Membership.Member |> Ash.Query.for_read(:available_for_linking, %{ user_email: nil, search_query: "Jonatan" }) {:ok, members} = Ash.read(query, domain: Mv.Membership, actor: actor) # Should find Jonathan despite typo assert length(members) == 1 assert hd(members).id == member.id end test "finds member with partial match", %{actor: actor} do # Create member {:ok, member} = Membership.create_member( %{ first_name: "Alexander", last_name: "Williams", email: "alex@example.com" }, actor: actor ) # Search with partial query = Mv.Membership.Member |> Ash.Query.for_read(:available_for_linking, %{ user_email: nil, search_query: "Alex" }) {:ok, members} = Ash.read(query, domain: Mv.Membership, actor: actor) # Should find Alexander assert length(members) == 1 assert hd(members).id == member.id end test "email match overrides fuzzy search", %{actor: actor} do # Create two members {:ok, member1} = Membership.create_member( %{ first_name: "John", last_name: "Doe", email: "john@example.com" }, actor: actor ) {:ok, _member2} = Membership.create_member( %{ first_name: "Jane", last_name: "Smith", email: "jane@example.com" }, actor: actor ) # Search with user_email that matches member1, but search_query that would match member2 query = Mv.Membership.Member |> Ash.Query.for_read(:available_for_linking, %{ user_email: "john@example.com", search_query: "Jane" }) {:ok, members} = Ash.read(query, domain: Mv.Membership, actor: actor) # Apply email filter filtered_members = Mv.Membership.Member.filter_by_email_match(members, "john@example.com") # Should only return member1 (email match takes precedence) assert length(filtered_members) == 1 assert hd(filtered_members).id == member1.id end test "limits to 10 results", %{actor: actor} do # Create 15 members with similar names for i <- 1..15 do Membership.create_member( %{ first_name: "Test#{i}", last_name: "Member", email: "test#{i}@example.com" }, actor: actor ) end # Search for "Test" query = Mv.Membership.Member |> Ash.Query.for_read(:available_for_linking, %{ user_email: nil, search_query: "Test" }) {:ok, members} = Ash.read(query, domain: Mv.Membership, actor: actor) # Should return max 10 members assert length(members) == 10 end test "excludes linked members", %{actor: actor} do # Create member and link to user {:ok, member1} = Membership.create_member( %{ first_name: "Linked", last_name: "Member", email: "linked@example.com" }, actor: actor ) {:ok, _user} = Accounts.create_user( %{ email: "user@example.com", member: %{id: member1.id} }, actor: actor ) # Create unlinked member {:ok, member2} = Membership.create_member( %{ first_name: "Unlinked", last_name: "Member", email: "unlinked@example.com" }, actor: actor ) # Search for "Member" query = Mv.Membership.Member |> Ash.Query.for_read(:available_for_linking, %{ user_email: nil, search_query: "Member" }) {:ok, members} = Ash.read(query, domain: Mv.Membership, actor: actor) # Should only return unlinked member member_ids = Enum.map(members, & &1.id) refute member1.id in member_ids assert member2.id in member_ids end end end