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.

418 lines
14KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
  5. use Pleroma.Web.ConnCase
  6. alias Pleroma.Object
  7. alias Pleroma.Web.CommonAPI
  8. alias Pleroma.Web.Endpoint
  9. import Pleroma.Factory
  10. import ExUnit.CaptureLog
  11. import Tesla.Mock
  12. import Mock
  13. setup_all do
  14. mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
  15. :ok
  16. end
  17. describe ".search2" do
  18. test "it returns empty result if user or status search return undefined error", %{conn: conn} do
  19. with_mocks [
  20. {Pleroma.User, [], [search: fn _q, _o -> raise "Oops" end]},
  21. {Pleroma.Activity, [], [search: fn _u, _q, _o -> raise "Oops" end]}
  22. ] do
  23. capture_log(fn ->
  24. results =
  25. conn
  26. |> get("/api/v2/search?q=2hu")
  27. |> json_response_and_validate_schema(200)
  28. assert results["accounts"] == []
  29. assert results["statuses"] == []
  30. end) =~
  31. "[error] Elixir.Pleroma.Web.MastodonAPI.SearchController search error: %RuntimeError{message: \"Oops\"}"
  32. end
  33. end
  34. test "search", %{conn: conn} do
  35. user = insert(:user)
  36. user_two = insert(:user, %{nickname: "shp@shitposter.club"})
  37. user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  38. {:ok, activity} = CommonAPI.post(user, %{status: "This is about 2hu private 天子"})
  39. {:ok, _activity} =
  40. CommonAPI.post(user, %{
  41. status: "This is about 2hu, but private",
  42. visibility: "private"
  43. })
  44. {:ok, _} = CommonAPI.post(user_two, %{status: "This isn't"})
  45. results =
  46. conn
  47. |> get("/api/v2/search?#{URI.encode_query(%{q: "2hu #private"})}")
  48. |> json_response_and_validate_schema(200)
  49. [account | _] = results["accounts"]
  50. assert account["id"] == to_string(user_three.id)
  51. assert results["hashtags"] == [
  52. %{"name" => "private", "url" => "#{Endpoint.url()}/tag/private"}
  53. ]
  54. [status] = results["statuses"]
  55. assert status["id"] == to_string(activity.id)
  56. results =
  57. get(conn, "/api/v2/search?q=天子")
  58. |> json_response_and_validate_schema(200)
  59. assert results["hashtags"] == [
  60. %{"name" => "天子", "url" => "#{Endpoint.url()}/tag/天子"}
  61. ]
  62. [status] = results["statuses"]
  63. assert status["id"] == to_string(activity.id)
  64. end
  65. @tag capture_log: true
  66. test "constructs hashtags from search query", %{conn: conn} do
  67. results =
  68. conn
  69. |> get("/api/v2/search?#{URI.encode_query(%{q: "some text with #explicit #hashtags"})}")
  70. |> json_response_and_validate_schema(200)
  71. assert results["hashtags"] == [
  72. %{"name" => "explicit", "url" => "#{Endpoint.url()}/tag/explicit"},
  73. %{"name" => "hashtags", "url" => "#{Endpoint.url()}/tag/hashtags"}
  74. ]
  75. results =
  76. conn
  77. |> get("/api/v2/search?#{URI.encode_query(%{q: "john doe JOHN DOE"})}")
  78. |> json_response_and_validate_schema(200)
  79. assert results["hashtags"] == [
  80. %{"name" => "john", "url" => "#{Endpoint.url()}/tag/john"},
  81. %{"name" => "doe", "url" => "#{Endpoint.url()}/tag/doe"},
  82. %{"name" => "JohnDoe", "url" => "#{Endpoint.url()}/tag/JohnDoe"}
  83. ]
  84. results =
  85. conn
  86. |> get("/api/v2/search?#{URI.encode_query(%{q: "accident-prone"})}")
  87. |> json_response_and_validate_schema(200)
  88. assert results["hashtags"] == [
  89. %{"name" => "accident", "url" => "#{Endpoint.url()}/tag/accident"},
  90. %{"name" => "prone", "url" => "#{Endpoint.url()}/tag/prone"},
  91. %{"name" => "AccidentProne", "url" => "#{Endpoint.url()}/tag/AccidentProne"}
  92. ]
  93. results =
  94. conn
  95. |> get("/api/v2/search?#{URI.encode_query(%{q: "https://shpposter.club/users/shpuld"})}")
  96. |> json_response_and_validate_schema(200)
  97. assert results["hashtags"] == [
  98. %{"name" => "shpuld", "url" => "#{Endpoint.url()}/tag/shpuld"}
  99. ]
  100. results =
  101. conn
  102. |> get(
  103. "/api/v2/search?#{
  104. URI.encode_query(%{
  105. q:
  106. "https://www.washingtonpost.com/sports/2020/06/10/" <>
  107. "nascar-ban-display-confederate-flag-all-events-properties/"
  108. })
  109. }"
  110. )
  111. |> json_response_and_validate_schema(200)
  112. assert results["hashtags"] == [
  113. %{"name" => "nascar", "url" => "#{Endpoint.url()}/tag/nascar"},
  114. %{"name" => "ban", "url" => "#{Endpoint.url()}/tag/ban"},
  115. %{"name" => "display", "url" => "#{Endpoint.url()}/tag/display"},
  116. %{"name" => "confederate", "url" => "#{Endpoint.url()}/tag/confederate"},
  117. %{"name" => "flag", "url" => "#{Endpoint.url()}/tag/flag"},
  118. %{"name" => "all", "url" => "#{Endpoint.url()}/tag/all"},
  119. %{"name" => "events", "url" => "#{Endpoint.url()}/tag/events"},
  120. %{"name" => "properties", "url" => "#{Endpoint.url()}/tag/properties"},
  121. %{
  122. "name" => "NascarBanDisplayConfederateFlagAllEventsProperties",
  123. "url" =>
  124. "#{Endpoint.url()}/tag/NascarBanDisplayConfederateFlagAllEventsProperties"
  125. }
  126. ]
  127. end
  128. test "supports pagination of hashtags search results", %{conn: conn} do
  129. results =
  130. conn
  131. |> get(
  132. "/api/v2/search?#{
  133. URI.encode_query(%{q: "#some #text #with #hashtags", limit: 2, offset: 1})
  134. }"
  135. )
  136. |> json_response_and_validate_schema(200)
  137. assert results["hashtags"] == [
  138. %{"name" => "text", "url" => "#{Endpoint.url()}/tag/text"},
  139. %{"name" => "with", "url" => "#{Endpoint.url()}/tag/with"}
  140. ]
  141. end
  142. test "excludes a blocked users from search results", %{conn: conn} do
  143. user = insert(:user)
  144. user_smith = insert(:user, %{nickname: "Agent", name: "I love 2hu"})
  145. user_neo = insert(:user, %{nickname: "Agent Neo", name: "Agent"})
  146. {:ok, act1} = CommonAPI.post(user, %{status: "This is about 2hu private 天子"})
  147. {:ok, act2} = CommonAPI.post(user_smith, %{status: "Agent Smith"})
  148. {:ok, act3} = CommonAPI.post(user_neo, %{status: "Agent Smith"})
  149. Pleroma.User.block(user, user_smith)
  150. results =
  151. conn
  152. |> assign(:user, user)
  153. |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
  154. |> get("/api/v2/search?q=Agent")
  155. |> json_response_and_validate_schema(200)
  156. status_ids = Enum.map(results["statuses"], fn g -> g["id"] end)
  157. assert act3.id in status_ids
  158. refute act2.id in status_ids
  159. refute act1.id in status_ids
  160. end
  161. end
  162. describe ".account_search" do
  163. test "account search", %{conn: conn} do
  164. user_two = insert(:user, %{nickname: "shp@shitposter.club"})
  165. user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  166. results =
  167. conn
  168. |> get("/api/v1/accounts/search?q=shp")
  169. |> json_response_and_validate_schema(200)
  170. result_ids = for result <- results, do: result["acct"]
  171. assert user_two.nickname in result_ids
  172. assert user_three.nickname in result_ids
  173. results =
  174. conn
  175. |> get("/api/v1/accounts/search?q=2hu")
  176. |> json_response_and_validate_schema(200)
  177. result_ids = for result <- results, do: result["acct"]
  178. assert user_three.nickname in result_ids
  179. end
  180. test "returns account if query contains a space", %{conn: conn} do
  181. insert(:user, %{nickname: "shp@shitposter.club"})
  182. results =
  183. conn
  184. |> get("/api/v1/accounts/search?q=shp@shitposter.club xxx")
  185. |> json_response_and_validate_schema(200)
  186. assert length(results) == 1
  187. end
  188. end
  189. describe ".search" do
  190. test "it returns empty result if user or status search return undefined error", %{conn: conn} do
  191. with_mocks [
  192. {Pleroma.User, [], [search: fn _q, _o -> raise "Oops" end]},
  193. {Pleroma.Activity, [], [search: fn _u, _q, _o -> raise "Oops" end]}
  194. ] do
  195. capture_log(fn ->
  196. results =
  197. conn
  198. |> get("/api/v1/search?q=2hu")
  199. |> json_response_and_validate_schema(200)
  200. assert results["accounts"] == []
  201. assert results["statuses"] == []
  202. end) =~
  203. "[error] Elixir.Pleroma.Web.MastodonAPI.SearchController search error: %RuntimeError{message: \"Oops\"}"
  204. end
  205. end
  206. test "search", %{conn: conn} do
  207. user = insert(:user)
  208. user_two = insert(:user, %{nickname: "shp@shitposter.club"})
  209. user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  210. {:ok, activity} = CommonAPI.post(user, %{status: "This is about 2hu"})
  211. {:ok, _activity} =
  212. CommonAPI.post(user, %{
  213. status: "This is about 2hu, but private",
  214. visibility: "private"
  215. })
  216. {:ok, _} = CommonAPI.post(user_two, %{status: "This isn't"})
  217. results =
  218. conn
  219. |> get("/api/v1/search?q=2hu")
  220. |> json_response_and_validate_schema(200)
  221. [account | _] = results["accounts"]
  222. assert account["id"] == to_string(user_three.id)
  223. assert results["hashtags"] == ["2hu"]
  224. [status] = results["statuses"]
  225. assert status["id"] == to_string(activity.id)
  226. end
  227. test "search fetches remote statuses and prefers them over other results", %{conn: conn} do
  228. old_version = :persistent_term.get({Pleroma.Repo, :postgres_version})
  229. :persistent_term.put({Pleroma.Repo, :postgres_version}, 10.0)
  230. on_exit(fn -> :persistent_term.put({Pleroma.Repo, :postgres_version}, old_version) end)
  231. capture_log(fn ->
  232. {:ok, %{id: activity_id}} =
  233. CommonAPI.post(insert(:user), %{
  234. status: "check out http://mastodon.example.org/@admin/99541947525187367"
  235. })
  236. results =
  237. conn
  238. |> get("/api/v1/search?q=http://mastodon.example.org/@admin/99541947525187367")
  239. |> json_response_and_validate_schema(200)
  240. assert [
  241. %{"url" => "http://mastodon.example.org/@admin/99541947525187367"},
  242. %{"id" => ^activity_id}
  243. ] = results["statuses"]
  244. end)
  245. end
  246. test "search doesn't show statuses that it shouldn't", %{conn: conn} do
  247. {:ok, activity} =
  248. CommonAPI.post(insert(:user), %{
  249. status: "This is about 2hu, but private",
  250. visibility: "private"
  251. })
  252. capture_log(fn ->
  253. q = Object.normalize(activity, fetch: false).data["id"]
  254. results =
  255. conn
  256. |> get("/api/v1/search?q=#{q}")
  257. |> json_response_and_validate_schema(200)
  258. [] = results["statuses"]
  259. end)
  260. end
  261. test "search fetches remote accounts", %{conn: conn} do
  262. user = insert(:user)
  263. query = URI.encode_query(%{q: " mike@osada.macgirvin.com ", resolve: true})
  264. results =
  265. conn
  266. |> assign(:user, user)
  267. |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
  268. |> get("/api/v1/search?#{query}")
  269. |> json_response_and_validate_schema(200)
  270. [account] = results["accounts"]
  271. assert account["acct"] == "mike@osada.macgirvin.com"
  272. end
  273. test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
  274. results =
  275. conn
  276. |> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=false")
  277. |> json_response_and_validate_schema(200)
  278. assert [] == results["accounts"]
  279. end
  280. test "search with limit and offset", %{conn: conn} do
  281. user = insert(:user)
  282. _user_two = insert(:user, %{nickname: "shp@shitposter.club"})
  283. _user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  284. {:ok, _activity1} = CommonAPI.post(user, %{status: "This is about 2hu"})
  285. {:ok, _activity2} = CommonAPI.post(user, %{status: "This is also about 2hu"})
  286. result =
  287. conn
  288. |> get("/api/v1/search?q=2hu&limit=1")
  289. assert results = json_response_and_validate_schema(result, 200)
  290. assert [%{"id" => activity_id1}] = results["statuses"]
  291. assert [_] = results["accounts"]
  292. results =
  293. conn
  294. |> get("/api/v1/search?q=2hu&limit=1&offset=1")
  295. |> json_response_and_validate_schema(200)
  296. assert [%{"id" => activity_id2}] = results["statuses"]
  297. assert [] = results["accounts"]
  298. assert activity_id1 != activity_id2
  299. end
  300. test "search returns results only for the given type", %{conn: conn} do
  301. user = insert(:user)
  302. _user_two = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  303. {:ok, _activity} = CommonAPI.post(user, %{status: "This is about 2hu"})
  304. assert %{"statuses" => [_activity], "accounts" => [], "hashtags" => []} =
  305. conn
  306. |> get("/api/v1/search?q=2hu&type=statuses")
  307. |> json_response_and_validate_schema(200)
  308. assert %{"statuses" => [], "accounts" => [_user_two], "hashtags" => []} =
  309. conn
  310. |> get("/api/v1/search?q=2hu&type=accounts")
  311. |> json_response_and_validate_schema(200)
  312. end
  313. test "search uses account_id to filter statuses by the author", %{conn: conn} do
  314. user = insert(:user, %{nickname: "shp@shitposter.club"})
  315. user_two = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  316. {:ok, activity1} = CommonAPI.post(user, %{status: "This is about 2hu"})
  317. {:ok, activity2} = CommonAPI.post(user_two, %{status: "This is also about 2hu"})
  318. results =
  319. conn
  320. |> get("/api/v1/search?q=2hu&account_id=#{user.id}")
  321. |> json_response_and_validate_schema(200)
  322. assert [%{"id" => activity_id1}] = results["statuses"]
  323. assert activity_id1 == activity1.id
  324. assert [_] = results["accounts"]
  325. results =
  326. conn
  327. |> get("/api/v1/search?q=2hu&account_id=#{user_two.id}")
  328. |> json_response_and_validate_schema(200)
  329. assert [%{"id" => activity_id2}] = results["statuses"]
  330. assert activity_id2 == activity2.id
  331. end
  332. end
  333. end