logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/

search_controller_test.exs (15571B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 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. import Pleroma.Factory
  9. import ExUnit.CaptureLog
  10. import Tesla.Mock
  11. import Mock
  12. setup do
  13. Mox.stub_with(Pleroma.UnstubbedConfigMock, Pleroma.Test.StaticConfig)
  14. :ok
  15. end
  16. setup_all do
  17. mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
  18. :ok
  19. end
  20. describe ".search2" do
  21. test "it returns empty result if user or status search return undefined error", %{conn: conn} do
  22. with_mocks [
  23. {Pleroma.User, [], [search: fn _q, _o -> raise "Oops" end]},
  24. {Pleroma.Activity, [], [search: fn _u, _q, _o -> raise "Oops" end]}
  25. ] do
  26. capture_log(fn ->
  27. results =
  28. conn
  29. |> get("/api/v2/search?q=2hu")
  30. |> json_response_and_validate_schema(200)
  31. assert results["accounts"] == []
  32. assert results["statuses"] == []
  33. end) =~
  34. "[error] Elixir.Pleroma.Web.MastodonAPI.SearchController search error: %RuntimeError{message: \"Oops\"}"
  35. end
  36. end
  37. @tag :skip_darwin
  38. test "search", %{conn: conn} do
  39. user = insert(:user)
  40. user_two = insert(:user, %{nickname: "shp@shitposter.club"})
  41. user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  42. {:ok, activity} = CommonAPI.post(user, %{status: "This is about 2hu private 天子"})
  43. {:ok, _activity} =
  44. CommonAPI.post(user, %{
  45. status: "This is about 2hu, but private",
  46. visibility: "private"
  47. })
  48. {:ok, _} = CommonAPI.post(user_two, %{status: "This isn't"})
  49. results =
  50. conn
  51. |> get("/api/v2/search?#{URI.encode_query(%{q: "2hu #private"})}")
  52. |> json_response_and_validate_schema(200)
  53. [account | _] = results["accounts"]
  54. assert account["id"] == to_string(user_three.id)
  55. assert results["hashtags"] == []
  56. [status] = results["statuses"]
  57. assert status["id"] == to_string(activity.id)
  58. results =
  59. get(conn, "/api/v2/search?q=天子")
  60. |> json_response_and_validate_schema(200)
  61. assert results["hashtags"] == []
  62. [status] = results["statuses"]
  63. assert status["id"] == to_string(activity.id)
  64. end
  65. test "search local-only status as an authenticated user" do
  66. user = insert(:user)
  67. %{conn: conn} = oauth_access(["read:search"])
  68. {:ok, activity} =
  69. CommonAPI.post(user, %{status: "This is about 2hu private 天子", visibility: "local"})
  70. results =
  71. conn
  72. |> get("/api/v2/search?#{URI.encode_query(%{q: "2hu"})}")
  73. |> json_response_and_validate_schema(200)
  74. [status] = results["statuses"]
  75. assert status["id"] == to_string(activity.id)
  76. end
  77. test "search local-only status as an unauthenticated user" do
  78. user = insert(:user)
  79. %{conn: conn} = oauth_access([])
  80. {:ok, _activity} =
  81. CommonAPI.post(user, %{status: "This is about 2hu private 天子", visibility: "local"})
  82. results =
  83. conn
  84. |> get("/api/v2/search?#{URI.encode_query(%{q: "2hu"})}")
  85. |> json_response_and_validate_schema(200)
  86. assert [] = results["statuses"]
  87. end
  88. test "search local-only status as an anonymous user" do
  89. user = insert(:user)
  90. {:ok, _activity} =
  91. CommonAPI.post(user, %{status: "This is about 2hu private 天子", visibility: "local"})
  92. results =
  93. build_conn()
  94. |> get("/api/v2/search?#{URI.encode_query(%{q: "2hu"})}")
  95. |> json_response_and_validate_schema(200)
  96. assert [] = results["statuses"]
  97. end
  98. test "returns empty results when no hashtags match", %{conn: conn} do
  99. results =
  100. conn
  101. |> get("/api/v2/search?#{URI.encode_query(%{q: "nonexistent"})}")
  102. |> json_response_and_validate_schema(200)
  103. assert results["hashtags"] == []
  104. end
  105. test "searches hashtags by multiple words in query", %{conn: conn} do
  106. user = insert(:user)
  107. {:ok, _activity1} = CommonAPI.post(user, %{status: "This is my new #computer"})
  108. {:ok, _activity2} = CommonAPI.post(user, %{status: "Check out this #laptop"})
  109. {:ok, _activity3} = CommonAPI.post(user, %{status: "My #desktop setup"})
  110. {:ok, _activity4} = CommonAPI.post(user, %{status: "New #phone arrived"})
  111. results =
  112. conn
  113. |> get("/api/v2/search?#{URI.encode_query(%{q: "new computer"})}")
  114. |> json_response_and_validate_schema(200)
  115. hashtag_names = Enum.map(results["hashtags"], & &1["name"])
  116. assert "computer" in hashtag_names
  117. refute "laptop" in hashtag_names
  118. refute "desktop" in hashtag_names
  119. refute "phone" in hashtag_names
  120. results =
  121. conn
  122. |> get("/api/v2/search?#{URI.encode_query(%{q: "computer laptop"})}")
  123. |> json_response_and_validate_schema(200)
  124. hashtag_names = Enum.map(results["hashtags"], & &1["name"])
  125. assert "computer" in hashtag_names
  126. assert "laptop" in hashtag_names
  127. refute "desktop" in hashtag_names
  128. refute "phone" in hashtag_names
  129. end
  130. test "supports pagination of hashtags search results", %{conn: conn} do
  131. user = insert(:user)
  132. {:ok, _activity1} = CommonAPI.post(user, %{status: "First #alpha hashtag"})
  133. {:ok, _activity2} = CommonAPI.post(user, %{status: "Second #beta hashtag"})
  134. {:ok, _activity3} = CommonAPI.post(user, %{status: "Third #gamma hashtag"})
  135. {:ok, _activity4} = CommonAPI.post(user, %{status: "Fourth #delta hashtag"})
  136. results =
  137. conn
  138. |> get("/api/v2/search?#{URI.encode_query(%{q: "a", limit: 2, offset: 1})}")
  139. |> json_response_and_validate_schema(200)
  140. hashtag_names = Enum.map(results["hashtags"], & &1["name"])
  141. # Should return 2 hashtags (alpha, beta, gamma, delta all contain 'a')
  142. # With offset 1, we skip the first one, so we get 2 of the remaining 3
  143. assert length(hashtag_names) == 2
  144. assert Enum.all?(hashtag_names, &String.contains?(&1, "a"))
  145. end
  146. test "searches real hashtags from database", %{conn: conn} do
  147. user = insert(:user)
  148. {:ok, _activity1} = CommonAPI.post(user, %{status: "Check out this #car"})
  149. {:ok, _activity2} = CommonAPI.post(user, %{status: "Fast #racecar on the track"})
  150. {:ok, _activity3} = CommonAPI.post(user, %{status: "NASCAR #nascar racing"})
  151. results =
  152. conn
  153. |> get("/api/v2/search?#{URI.encode_query(%{q: "car"})}")
  154. |> json_response_and_validate_schema(200)
  155. hashtag_names = Enum.map(results["hashtags"], & &1["name"])
  156. # Should return car, racecar, and nascar since they all contain "car"
  157. assert "car" in hashtag_names
  158. assert "racecar" in hashtag_names
  159. assert "nascar" in hashtag_names
  160. # Search for "race" - should return racecar
  161. results =
  162. conn
  163. |> get("/api/v2/search?#{URI.encode_query(%{q: "race"})}")
  164. |> json_response_and_validate_schema(200)
  165. hashtag_names = Enum.map(results["hashtags"], & &1["name"])
  166. assert "racecar" in hashtag_names
  167. refute "car" in hashtag_names
  168. refute "nascar" in hashtag_names
  169. end
  170. test "excludes a blocked users from search results", %{conn: conn} do
  171. user = insert(:user)
  172. user_smith = insert(:user, %{nickname: "Agent", name: "I love 2hu"})
  173. user_neo = insert(:user, %{nickname: "Agent Neo", name: "Agent"})
  174. {:ok, act1} = CommonAPI.post(user, %{status: "This is about 2hu private 天子"})
  175. {:ok, act2} = CommonAPI.post(user_smith, %{status: "Agent Smith"})
  176. {:ok, act3} = CommonAPI.post(user_neo, %{status: "Agent Smith"})
  177. Pleroma.User.block(user, user_smith)
  178. results =
  179. conn
  180. |> assign(:user, user)
  181. |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
  182. |> get("/api/v2/search?q=Agent")
  183. |> json_response_and_validate_schema(200)
  184. status_ids = Enum.map(results["statuses"], fn g -> g["id"] end)
  185. assert act3.id in status_ids
  186. refute act2.id in status_ids
  187. refute act1.id in status_ids
  188. end
  189. end
  190. describe ".account_search" do
  191. test "account search", %{conn: conn} do
  192. user_two = insert(:user, %{nickname: "shp@shitposter.club"})
  193. user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  194. results =
  195. conn
  196. |> get("/api/v1/accounts/search?q=shp")
  197. |> json_response_and_validate_schema(200)
  198. result_ids = for result <- results, do: result["acct"]
  199. assert user_two.nickname in result_ids
  200. assert user_three.nickname in result_ids
  201. results =
  202. conn
  203. |> get("/api/v1/accounts/search?q=2hu")
  204. |> json_response_and_validate_schema(200)
  205. result_ids = for result <- results, do: result["acct"]
  206. assert user_three.nickname in result_ids
  207. end
  208. test "returns account if query contains a space", %{conn: conn} do
  209. insert(:user, %{nickname: "shp@shitposter.club"})
  210. results =
  211. conn
  212. |> get("/api/v1/accounts/search?q=shp@shitposter.club xxx")
  213. |> json_response_and_validate_schema(200)
  214. assert length(results) == 1
  215. end
  216. end
  217. describe ".search" do
  218. test "it returns empty result if user or status search return undefined error", %{conn: conn} do
  219. with_mocks [
  220. {Pleroma.User, [], [search: fn _q, _o -> raise "Oops" end]},
  221. {Pleroma.Activity, [], [search: fn _u, _q, _o -> raise "Oops" end]}
  222. ] do
  223. capture_log(fn ->
  224. results =
  225. conn
  226. |> get("/api/v1/search?q=2hu")
  227. |> json_response_and_validate_schema(200)
  228. assert results["accounts"] == []
  229. assert results["statuses"] == []
  230. end) =~
  231. "[error] Elixir.Pleroma.Web.MastodonAPI.SearchController search error: %RuntimeError{message: \"Oops\"}"
  232. end
  233. end
  234. test "search", %{conn: conn} do
  235. user = insert(:user)
  236. user_two = insert(:user, %{nickname: "shp@shitposter.club"})
  237. user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  238. {:ok, activity} = CommonAPI.post(user, %{status: "This is about 2hu"})
  239. {:ok, _activity} =
  240. CommonAPI.post(user, %{
  241. status: "This is about 2hu, but private",
  242. visibility: "private"
  243. })
  244. {:ok, _} = CommonAPI.post(user_two, %{status: "This isn't"})
  245. results =
  246. conn
  247. |> get("/api/v1/search?q=2hu")
  248. |> json_response_and_validate_schema(200)
  249. [account | _] = results["accounts"]
  250. assert account["id"] == to_string(user_three.id)
  251. assert results["hashtags"] == []
  252. [status] = results["statuses"]
  253. assert status["id"] == to_string(activity.id)
  254. end
  255. test "search fetches remote statuses and prefers them over other results", %{conn: conn} do
  256. {:ok, %{id: activity_id}} =
  257. CommonAPI.post(insert(:user), %{
  258. status: "check out http://mastodon.example.org/@admin/99541947525187367"
  259. })
  260. %{"url" => result_url, "id" => result_id} =
  261. conn
  262. |> get("/api/v1/search?q=http://mastodon.example.org/@admin/99541947525187367")
  263. |> json_response_and_validate_schema(200)
  264. |> Map.get("statuses")
  265. |> List.first()
  266. refute match?(^result_id, activity_id)
  267. assert match?(^result_url, "http://mastodon.example.org/@admin/99541947525187367")
  268. end
  269. test "search doesn't show statuses that it shouldn't", %{conn: conn} do
  270. {:ok, activity} =
  271. CommonAPI.post(insert(:user), %{
  272. status: "This is about 2hu, but private",
  273. visibility: "private"
  274. })
  275. capture_log(fn ->
  276. q = Object.normalize(activity, fetch: false).data["id"]
  277. results =
  278. conn
  279. |> get("/api/v1/search?q=#{q}")
  280. |> json_response_and_validate_schema(200)
  281. [] = results["statuses"]
  282. end)
  283. end
  284. test "search fetches remote accounts", %{conn: conn} do
  285. user = insert(:user)
  286. query = URI.encode_query(%{q: " mike@osada.macgirvin.com ", resolve: true})
  287. results =
  288. conn
  289. |> assign(:user, user)
  290. |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
  291. |> get("/api/v1/search?#{query}")
  292. |> json_response_and_validate_schema(200)
  293. [account] = results["accounts"]
  294. assert account["acct"] == "mike@osada.macgirvin.com"
  295. end
  296. test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
  297. results =
  298. conn
  299. |> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=false")
  300. |> json_response_and_validate_schema(200)
  301. assert [] == results["accounts"]
  302. end
  303. test "search with limit and offset", %{conn: conn} do
  304. user = insert(:user)
  305. _user_two = insert(:user, %{nickname: "shp@shitposter.club"})
  306. _user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  307. {:ok, _activity1} = CommonAPI.post(user, %{status: "This is about 2hu"})
  308. {:ok, _activity2} = CommonAPI.post(user, %{status: "This is also about 2hu"})
  309. result =
  310. conn
  311. |> get("/api/v1/search?q=2hu&limit=1")
  312. assert results = json_response_and_validate_schema(result, 200)
  313. assert [%{"id" => activity_id1}] = results["statuses"]
  314. assert [_] = results["accounts"]
  315. results =
  316. conn
  317. |> get("/api/v1/search?q=2hu&limit=1&offset=1")
  318. |> json_response_and_validate_schema(200)
  319. assert [%{"id" => activity_id2}] = results["statuses"]
  320. assert [] = results["accounts"]
  321. assert activity_id1 != activity_id2
  322. end
  323. test "search returns results only for the given type", %{conn: conn} do
  324. user = insert(:user)
  325. _user_two = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  326. {:ok, _activity} = CommonAPI.post(user, %{status: "This is about 2hu"})
  327. assert %{"statuses" => [_activity], "accounts" => [], "hashtags" => []} =
  328. conn
  329. |> get("/api/v1/search?q=2hu&type=statuses")
  330. |> json_response_and_validate_schema(200)
  331. assert %{"statuses" => [], "accounts" => [_user_two], "hashtags" => []} =
  332. conn
  333. |> get("/api/v1/search?q=2hu&type=accounts")
  334. |> json_response_and_validate_schema(200)
  335. end
  336. test "search uses account_id to filter statuses by the author", %{conn: conn} do
  337. user = insert(:user, %{nickname: "shp@shitposter.club"})
  338. user_two = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
  339. {:ok, activity1} = CommonAPI.post(user, %{status: "This is about 2hu"})
  340. {:ok, activity2} = CommonAPI.post(user_two, %{status: "This is also about 2hu"})
  341. results =
  342. conn
  343. |> get("/api/v1/search?q=2hu&account_id=#{user.id}")
  344. |> json_response_and_validate_schema(200)
  345. assert [%{"id" => activity_id1}] = results["statuses"]
  346. assert activity_id1 == activity1.id
  347. assert [_] = results["accounts"]
  348. results =
  349. conn
  350. |> get("/api/v1/search?q=2hu&account_id=#{user_two.id}")
  351. |> json_response_and_validate_schema(200)
  352. assert [%{"id" => activity_id2}] = results["statuses"]
  353. assert activity_id2 == activity2.id
  354. end
  355. end
  356. end