Implement search with PostgreSQL trigrams

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

Rename function to reflect that we only get one result

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

Add loggers and make Ecto call parallels during search

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

Implement trigrams for events & replace pg similarity operator % with <%

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

Fix tests

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2019-02-21 18:11:49 +01:00
parent 131152abac
commit 4ec40d601b
18 changed files with 422 additions and 141 deletions

View File

@@ -44,7 +44,7 @@ defmodule Mobilizon.ActorsTest do
setup do
user = insert(:user)
actor = insert(:actor, user: user)
actor = insert(:actor, user: user, preferred_username: "tcit")
{:ok, actor: actor}
end
@@ -177,11 +177,10 @@ defmodule Mobilizon.ActorsTest do
test "test find_local_by_username/1 returns local actors with similar usernames", %{
actor: actor
} do
actor2 = insert(:actor)
[%Actor{id: actor_found_id} | tail] = Actors.find_local_by_username("thomas")
actor2 = insert(:actor, preferred_username: "tcit")
[%Actor{id: actor_found_id} | tail] = Actors.find_local_by_username("tcit")
%Actor{id: actor2_found_id} = hd(tail)
assert actor_found_id == actor.id
assert actor2_found_id == actor2.id
assert MapSet.new([actor_found_id, actor2_found_id]) == MapSet.new([actor.id, actor2.id])
end
test "test find_actors_by_username_or_name/1 returns actors with similar usernames", %{
@@ -189,7 +188,7 @@ defmodule Mobilizon.ActorsTest do
} do
use_cassette "actors/remote_actor_mastodon_tcit" do
with {:ok, %Actor{id: actor2_id}} <- Actors.get_or_fetch_by_url(@remote_account_url) do
actors_ids = Actors.find_actors_by_username_or_name("t") |> Enum.map(& &1.id)
actors_ids = Actors.find_actors_by_username_or_name("tcit") |> Enum.map(& &1.id)
assert MapSet.new(actors_ids) == MapSet.new([actor2_id, actor_id])
end
end
@@ -200,26 +199,6 @@ defmodule Mobilizon.ActorsTest do
assert actors == []
end
test "test search/1 returns accounts for search with existing accounts", %{actor: actor} do
with {:ok, [%Actor{id: actor_found_id}]} <- Actors.search("t") do
assert actor_found_id == actor.id
end
end
test "test search/1 returns accounts for search with non existent accounts" do
assert {:ok, []} == Actors.search("nonexistent")
end
test "test search/1 returns accounts for search with existing remote accounts" do
with {:ok, [%Actor{preferred_username: username}]} <- Actors.search("tcit@framapiaf.org") do
assert username == "tcit"
end
end
test "test search/1 returns accounts for search with non existent remote accounts" do
assert {:error, "No ActivityPub URL found in WebFinger"} == Actors.search("tcit@yolo.tld")
end
test "test get_public_key_for_url/1 with local actor", %{actor: actor} do
assert Actor.get_public_key_for_url(actor.url) ==
actor.keys |> Mobilizon.Actors.Actor.prepare_public_key()

View File

@@ -59,12 +59,12 @@ defmodule Mobilizon.EventsTest do
assert title == hd(Events.find_events_by_name(event.title)).title
%Event{title: title2} = event2 = insert(:event, title: "Special event")
assert event2.title == hd(Events.find_events_by_name("Special")).title
assert event2.title == Events.find_events_by_name("Special") |> hd() |> Map.get(:title)
assert event2.title == hd(Events.find_events_by_name(" Special ")).title
assert event2.title == Events.find_events_by_name(" Special ") |> hd() |> Map.get(:title)
assert title == hd(Events.find_events_by_name("")).title
assert title2 == hd(tl(Events.find_events_by_name(""))).title
assert title == Events.find_events_by_name("") |> hd() |> Map.get(:title)
assert title2 == Events.find_events_by_name("") |> tl |> hd() |> Map.get(:title)
end
test "find_close_events/3 returns events in the area" do

View File

@@ -0,0 +1,55 @@
defmodule MobilizonWeb.API.SearchTest do
use ExUnit.Case, async: false
alias Mobilizon.Events
alias Mobilizon.Events.Event
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.ActivityPub
alias MobilizonWeb.API.Search
import Mock
test "search an user by username" do
with_mock ActivityPub,
find_or_make_actor_from_nickname: fn "toto@domain.tld" -> {:ok, %Actor{id: 1}} end do
assert {:ok, %Actor{id: 1}} == Search.search("toto@domain.tld")
assert_called(ActivityPub.find_or_make_actor_from_nickname("toto@domain.tld"))
end
end
test "search something by URL" do
with_mock ActivityPub,
fetch_object_from_url: fn "https://social.tcit.fr/users/tcit" -> {:ok, %Actor{id: 1}} end do
assert {:ok, %Actor{id: 1}} == Search.search("https://social.tcit.fr/users/tcit")
assert_called(ActivityPub.fetch_object_from_url("https://social.tcit.fr/users/tcit"))
end
end
test "search everything" do
with_mocks([
{Actors, [], [find_actors_by_username_or_name: fn "toto", 1, 10 -> [%Actor{}] end]},
{Events, [], [find_events_by_name: fn "toto", 1, 10 -> [%Event{}] end]}
]) do
assert {:ok, [%Event{}, %Actor{}]} = Search.search("toto")
assert_called(Actors.find_actors_by_username_or_name("toto", 1, 10))
assert_called(Events.find_events_by_name("toto", 1, 10))
end
end
test "search actors" do
with_mock Actors,
find_actors_by_username_or_name: fn "toto", 1, 10 -> [%Actor{}] end do
assert {:ok, [%Actor{}]} = Search.search_actors("toto")
assert_called(Actors.find_actors_by_username_or_name("toto", 1, 10))
end
end
test "search events" do
with_mock Events,
find_events_by_name: fn "toto", 1, 10 -> [%Event{}] end do
assert {:ok, [%Event{}]} = Search.search_events("toto")
assert_called(Events.find_events_by_name("toto", 1, 10))
end
end
end

View File

@@ -88,35 +88,6 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
assert json_response(res, 200)["data"]["createEvent"]["title"] == "come to my event"
end
test "search_events_and_actors/3 finds events and actors", %{conn: conn, actor: actor} do
event = insert(:event, title: "test")
query = """
{
search(search: "test") {
...on Event {
title,
uuid,
__typename
},
...on Actor {
preferredUsername,
__typename
}
}
}
"""
res =
conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "search"))
assert hd(json_response(res, 200)["data"]["search"])["uuid"] == to_string(event.uuid)
assert hd(tl(json_response(res, 200)["data"]["search"]))["preferredUsername"] ==
actor.preferred_username
end
test "list_events/3 returns events", context do
event = insert(:event)

View File

@@ -0,0 +1,109 @@
defmodule MobilizonWeb.Resolvers.SearchResolverTest do
use MobilizonWeb.ConnCase
alias MobilizonWeb.AbsintheHelpers
import Mobilizon.Factory
setup %{conn: conn} do
user = insert(:user)
{:ok, conn: conn, user: user}
end
test "search_events_and_actors/3 finds events and actors with basic search", %{
conn: conn,
user: user
} do
actor = insert(:actor, user: user, preferred_username: "test")
event = insert(:event, title: "test")
query = """
{
search(search: "test") {
...on Event {
title,
uuid,
__typename
},
...on Actor {
preferredUsername,
__typename
}
}
}
"""
res =
conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "search"))
assert hd(json_response(res, 200)["data"]["search"])["uuid"] == to_string(event.uuid)
assert hd(tl(json_response(res, 200)["data"]["search"]))["preferredUsername"] ==
actor.preferred_username
end
test "search_events_and_actors/3 finds events and actors with word search", %{
conn: conn,
user: user
} do
actor = insert(:actor, user: user, preferred_username: "toto", name: "I like pineapples")
event = insert(:event, title: "Pineapple fashion week")
# Elaborate query
query = """
{
search(search: "pineapple") {
...on Event {
title,
uuid,
__typename
},
...on Actor {
preferredUsername,
__typename
}
}
}
"""
res =
conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "search"))
assert hd(json_response(res, 200)["data"]["search"])["uuid"] == to_string(event.uuid)
assert hd(tl(json_response(res, 200)["data"]["search"]))["preferredUsername"] ==
actor.preferred_username
end
test "search_events_and_actors/3 finds events and actors with accented search", %{
conn: conn,
user: user
} do
insert(:actor, user: user, preferred_username: "toto", name: "Torréfaction")
event = insert(:event, title: "Tour du monde des cafés")
# Elaborate query
query = """
{
search(search: "café") {
...on Event {
title,
uuid,
__typename
},
...on Actor {
preferredUsername,
__typename
}
}
}
"""
res =
conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "search"))
assert hd(json_response(res, 200)["data"]["search"])["uuid"] == to_string(event.uuid)
end
end