diff --git a/test/membership/fuzzy_search_test.exs b/test/membership/fuzzy_search_test.exs new file mode 100644 index 0000000..9e746d3 --- /dev/null +++ b/test/membership/fuzzy_search_test.exs @@ -0,0 +1,164 @@ +defmodule Mv.Membership.FuzzySearchTest do + use Mv.DataCase, async: false + + test "fuzzy_search/2 function exists" do + assert function_exported?(Mv.Membership.Member, :fuzzy_search, 2) + end + + test "fuzzy_search returns only John Doe by fuzzy query 'john'" do + {:ok, john} = + Mv.Membership.create_member(%{ + first_name: "John", + last_name: "Doe", + email: "john.doe@example.com" + }) + + {:ok, _jane} = + Mv.Membership.create_member(%{ + first_name: "Adriana", + last_name: "Smith", + email: "adriana.smith@example.com" + }) + + {:ok, alice} = + Mv.Membership.create_member(%{ + first_name: "Alice", + last_name: "Johnson", + email: "alice.johnson@example.com" + }) + + result = + Mv.Membership.Member + |> Mv.Membership.Member.fuzzy_search(%{query: "john", fields: [:first_name, :last_name, :email]}) + |> Ash.read!() + + assert Enum.map(result, & &1.id) == [john.id, alice.id] + end + + test "fuzzy_search finds 'Thomas' when searching misspelled 'tomas'" do + {:ok, thomas} = + Mv.Membership.create_member(%{ + first_name: "Thomas", + last_name: "Doe", + email: "john.doe@example.com" + }) + + {:ok, jane} = + Mv.Membership.create_member(%{ + first_name: "Jane", + last_name: "Smith", + email: "jane.smith@example.com" + }) + + {:ok, _alice} = + Mv.Membership.create_member(%{ + first_name: "Alice", + last_name: "Johnson", + email: "alice.johnson@example.com" + }) + + result = + Mv.Membership.Member + |> Mv.Membership.Member.fuzzy_search(%{query: "tomas", fields: [:first_name, :last_name, :email]}) + |> Ash.read!() + + ids = Enum.map(result, & &1.id) + assert thomas.id in ids + refute jane.id in ids + assert length(ids) >= 1 + end + + test "empty query returns all members" do + {:ok, a} = + Mv.Membership.create_member(%{first_name: "A", last_name: "One", email: "a1@example.com"}) + + {:ok, b} = + Mv.Membership.create_member(%{first_name: "B", last_name: "Two", email: "b2@example.com"}) + + result = + Mv.Membership.Member + |> Mv.Membership.Member.fuzzy_search(%{query: ""}) + |> Ash.read!() + + assert Enum.sort(Enum.map(result, & &1.id)) |> Enum.uniq() |> Enum.sort() + |> Enum.all?(fn id -> id in [a.id, b.id] end) + end + + test "substring numeric search matches postal_code mid-string" do + {:ok, m1} = + Mv.Membership.create_member(%{ + first_name: "Num", + last_name: "One", + email: "n1@example.com", + postal_code: "12345" + }) + + {:ok, _m2} = + Mv.Membership.create_member(%{ + first_name: "Num", + last_name: "Two", + email: "n2@example.com", + postal_code: "67890" + }) + + result = + Mv.Membership.Member + |> Mv.Membership.Member.fuzzy_search(%{query: "345"}) + |> Ash.read!() + + ids = Enum.map(result, & &1.id) + assert m1.id in ids + end + + test "substring numeric search matches house_number mid-string" do + {:ok, m1} = + Mv.Membership.create_member(%{ + first_name: "Home", + last_name: "One", + email: "h1@example.com", + house_number: "A345B" + }) + + {:ok, _m2} = + Mv.Membership.create_member(%{ + first_name: "Home", + last_name: "Two", + email: "h2@example.com", + house_number: "77" + }) + + result = + Mv.Membership.Member + |> Mv.Membership.Member.fuzzy_search(%{query: "345"}) + |> Ash.read!() + + ids = Enum.map(result, & &1.id) + assert m1.id in ids + end + + test "fuzzy matches street misspelling" do + {:ok, s1} = + Mv.Membership.create_member(%{ + first_name: "Road", + last_name: "Test", + email: "s1@example.com", + street: "Main Street" + }) + + {:ok, _s2} = + Mv.Membership.create_member(%{ + first_name: "Road", + last_name: "Other", + email: "s2@example.com", + street: "Second Avenue" + }) + + result = + Mv.Membership.Member + |> Mv.Membership.Member.fuzzy_search(%{query: "mainn"}) + |> Ash.read!() + + ids = Enum.map(result, & &1.id) + assert s1.id in ids + end +end