Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

315 lines
10KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.UserSearchTest do
  5. alias Pleroma.Repo
  6. alias Pleroma.User
  7. use Pleroma.DataCase
  8. import Pleroma.Factory
  9. setup_all do
  10. Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
  11. :ok
  12. end
  13. describe "User.search" do
  14. setup do: clear_config([:instance, :limit_to_local_content])
  15. test "excludes invisible users from results" do
  16. user = insert(:user, %{nickname: "john t1000"})
  17. insert(:user, %{invisible: true, nickname: "john t800"})
  18. [found_user] = User.search("john")
  19. assert found_user.id == user.id
  20. end
  21. test "excludes service actors from results" do
  22. insert(:user, actor_type: "Application", nickname: "user1")
  23. service = insert(:user, actor_type: "Service", nickname: "user2")
  24. person = insert(:user, actor_type: "Person", nickname: "user3")
  25. assert [found_user1, found_user2] = User.search("user")
  26. assert [found_user1.id, found_user2.id] -- [service.id, person.id] == []
  27. end
  28. test "accepts limit parameter" do
  29. Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
  30. assert length(User.search("john", limit: 3)) == 3
  31. assert length(User.search("john")) == 5
  32. end
  33. test "accepts offset parameter" do
  34. Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
  35. assert length(User.search("john", limit: 3)) == 3
  36. assert length(User.search("john", limit: 3, offset: 3)) == 2
  37. end
  38. defp clear_virtual_fields(user) do
  39. Map.merge(user, %{search_rank: nil, search_type: nil})
  40. end
  41. test "finds a user by full nickname or its leading fragment" do
  42. user = insert(:user, %{nickname: "john"})
  43. Enum.each(["john", "jo", "j"], fn query ->
  44. assert user ==
  45. User.search(query)
  46. |> List.first()
  47. |> clear_virtual_fields()
  48. end)
  49. end
  50. test "finds a user by full name or leading fragment(s) of its words" do
  51. user = insert(:user, %{name: "John Doe"})
  52. Enum.each(["John Doe", "JOHN", "doe", "j d", "j", "d"], fn query ->
  53. assert user ==
  54. User.search(query)
  55. |> List.first()
  56. |> clear_virtual_fields()
  57. end)
  58. end
  59. test "matches by leading fragment of user domain" do
  60. user = insert(:user, %{nickname: "arandom@dude.com"})
  61. insert(:user, %{nickname: "iamthedude"})
  62. assert [user.id] == User.search("dud") |> Enum.map(& &1.id)
  63. end
  64. test "ranks full nickname match higher than full name match" do
  65. nicknamed_user = insert(:user, %{nickname: "hj@shigusegubu.club"})
  66. named_user = insert(:user, %{nickname: "xyz@sample.com", name: "HJ"})
  67. results = User.search("hj")
  68. assert [nicknamed_user.id, named_user.id] == Enum.map(results, & &1.id)
  69. assert Enum.at(results, 0).search_rank > Enum.at(results, 1).search_rank
  70. end
  71. test "finds users, considering density of matched tokens" do
  72. u1 = insert(:user, %{name: "Bar Bar plus Word Word"})
  73. u2 = insert(:user, %{name: "Word Word Bar Bar Bar"})
  74. assert [u2.id, u1.id] == Enum.map(User.search("bar word"), & &1.id)
  75. end
  76. test "finds users, boosting ranks of friends and followers" do
  77. u1 = insert(:user)
  78. u2 = insert(:user, %{name: "Doe"})
  79. follower = insert(:user, %{name: "Doe"})
  80. friend = insert(:user, %{name: "Doe"})
  81. {:ok, follower} = User.follow(follower, u1)
  82. {:ok, u1} = User.follow(u1, friend)
  83. assert [friend.id, follower.id, u2.id] --
  84. Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []
  85. end
  86. test "finds followers of user by partial name" do
  87. u1 = insert(:user)
  88. u2 = insert(:user, %{name: "Jimi"})
  89. follower_jimi = insert(:user, %{name: "Jimi Hendrix"})
  90. follower_lizz = insert(:user, %{name: "Lizz Wright"})
  91. friend = insert(:user, %{name: "Jimi"})
  92. {:ok, follower_jimi} = User.follow(follower_jimi, u1)
  93. {:ok, _follower_lizz} = User.follow(follower_lizz, u2)
  94. {:ok, u1} = User.follow(u1, friend)
  95. assert Enum.map(User.search("jimi", following: true, for_user: u1), & &1.id) == [
  96. follower_jimi.id
  97. ]
  98. assert User.search("lizz", following: true, for_user: u1) == []
  99. end
  100. test "find local and remote users for authenticated users" do
  101. u1 = insert(:user, %{name: "lain"})
  102. u2 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
  103. u3 = insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
  104. results =
  105. "lain"
  106. |> User.search(for_user: u1)
  107. |> Enum.map(& &1.id)
  108. |> Enum.sort()
  109. assert [u1.id, u2.id, u3.id] == results
  110. end
  111. test "find only local users for unauthenticated users" do
  112. %{id: id} = insert(:user, %{name: "lain"})
  113. insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
  114. insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
  115. assert [%{id: ^id}] = User.search("lain")
  116. end
  117. test "find only local users for authenticated users when `limit_to_local_content` is `:all`" do
  118. Pleroma.Config.put([:instance, :limit_to_local_content], :all)
  119. %{id: id} = insert(:user, %{name: "lain"})
  120. insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
  121. insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
  122. assert [%{id: ^id}] = User.search("lain")
  123. end
  124. test "find all users for unauthenticated users when `limit_to_local_content` is `false`" do
  125. Pleroma.Config.put([:instance, :limit_to_local_content], false)
  126. u1 = insert(:user, %{name: "lain"})
  127. u2 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
  128. u3 = insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
  129. results =
  130. "lain"
  131. |> User.search()
  132. |> Enum.map(& &1.id)
  133. |> Enum.sort()
  134. assert [u1.id, u2.id, u3.id] == results
  135. end
  136. test "does not yield false-positive matches" do
  137. insert(:user, %{name: "John Doe"})
  138. Enum.each(["mary", "a", ""], fn query ->
  139. assert [] == User.search(query)
  140. end)
  141. end
  142. test "works with URIs" do
  143. user = insert(:user)
  144. results =
  145. User.search("http://mastodon.example.org/users/admin", resolve: true, for_user: user)
  146. result = results |> List.first()
  147. user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin")
  148. assert length(results) == 1
  149. expected =
  150. result
  151. |> Map.put(:search_rank, nil)
  152. |> Map.put(:search_type, nil)
  153. |> Map.put(:last_digest_emailed_at, nil)
  154. |> Map.put(:multi_factor_authentication_settings, nil)
  155. |> Map.put(:notification_settings, nil)
  156. assert user == expected
  157. end
  158. test "excludes a blocked users from search result" do
  159. user = insert(:user, %{nickname: "Bill"})
  160. [blocked_user | users] = Enum.map(0..3, &insert(:user, %{nickname: "john#{&1}"}))
  161. blocked_user2 =
  162. insert(
  163. :user,
  164. %{nickname: "john awful", ap_id: "https://awful-and-rude-instance.com/user/bully"}
  165. )
  166. User.block_domain(user, "awful-and-rude-instance.com")
  167. User.block(user, blocked_user)
  168. account_ids = User.search("john", for_user: refresh_record(user)) |> collect_ids
  169. assert account_ids == collect_ids(users)
  170. refute Enum.member?(account_ids, blocked_user.id)
  171. refute Enum.member?(account_ids, blocked_user2.id)
  172. assert length(account_ids) == 3
  173. end
  174. test "local user has the same search_rank as for users with the same nickname, but another domain" do
  175. user = insert(:user)
  176. insert(:user, nickname: "lain@mastodon.social")
  177. insert(:user, nickname: "lain")
  178. insert(:user, nickname: "lain@pleroma.social")
  179. assert User.search("lain@localhost", resolve: true, for_user: user)
  180. |> Enum.each(fn u -> u.search_rank == 0.5 end)
  181. end
  182. test "localhost is the part of the domain" do
  183. user = insert(:user)
  184. insert(:user, nickname: "another@somedomain")
  185. insert(:user, nickname: "lain")
  186. insert(:user, nickname: "lain@examplelocalhost")
  187. result = User.search("lain@examplelocalhost", resolve: true, for_user: user)
  188. assert Enum.each(result, fn u -> u.search_rank == 0.5 end)
  189. assert length(result) == 2
  190. end
  191. test "local user search with users" do
  192. user = insert(:user)
  193. local_user = insert(:user, nickname: "lain")
  194. insert(:user, nickname: "another@localhost.com")
  195. insert(:user, nickname: "localhost@localhost.com")
  196. [result] = User.search("lain@localhost", resolve: true, for_user: user)
  197. assert Map.put(result, :search_rank, nil) |> Map.put(:search_type, nil) == local_user
  198. end
  199. test "works with idna domains" do
  200. user = insert(:user, nickname: "lain@" <> to_string(:idna.encode("zetsubou.みんな")))
  201. results = User.search("lain@zetsubou.みんな", resolve: false, for_user: user)
  202. result = List.first(results)
  203. assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
  204. end
  205. test "works with idna domains converted input" do
  206. user = insert(:user, nickname: "lain@" <> to_string(:idna.encode("zetsubou.みんな")))
  207. results =
  208. User.search("lain@zetsubou." <> to_string(:idna.encode("zetsubou.みんな")),
  209. resolve: false,
  210. for_user: user
  211. )
  212. result = List.first(results)
  213. assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
  214. end
  215. test "works with idna domains and bad chars in domain" do
  216. user = insert(:user, nickname: "lain@" <> to_string(:idna.encode("zetsubou.みんな")))
  217. results =
  218. User.search("lain@zetsubou!@#$%^&*()+,-/:;<=>?[]'_{}|~`.みんな",
  219. resolve: false,
  220. for_user: user
  221. )
  222. result = List.first(results)
  223. assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
  224. end
  225. test "works with idna domains and query as link" do
  226. user = insert(:user, nickname: "lain@" <> to_string(:idna.encode("zetsubou.みんな")))
  227. results =
  228. User.search("https://zetsubou.みんな/users/lain",
  229. resolve: false,
  230. for_user: user
  231. )
  232. result = List.first(results)
  233. assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
  234. end
  235. end
  236. end