logo

pleroma

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

timeline_controller_test.exs (34924B)


  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.TimelineControllerTest do
  5. use Pleroma.Web.ConnCase
  6. import Pleroma.Factory
  7. import Tesla.Mock
  8. alias Pleroma.User
  9. alias Pleroma.Web.CommonAPI
  10. setup do
  11. mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
  12. :ok
  13. end
  14. describe "home" do
  15. setup do: oauth_access(["read:statuses"])
  16. test "does NOT embed account/pleroma/relationship in statuses", %{
  17. user: user,
  18. conn: conn
  19. } do
  20. other_user = insert(:user)
  21. {:ok, _} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
  22. response =
  23. conn
  24. |> assign(:user, user)
  25. |> get("/api/v1/timelines/home")
  26. |> json_response_and_validate_schema(200)
  27. assert Enum.all?(response, fn n ->
  28. get_in(n, ["account", "pleroma", "relationship"]) == %{}
  29. end)
  30. end
  31. test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do
  32. {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
  33. {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
  34. {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
  35. {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
  36. conn = get(conn, "/api/v1/timelines/home?exclude_visibilities[]=direct")
  37. assert status_ids = json_response_and_validate_schema(conn, :ok) |> Enum.map(& &1["id"])
  38. assert public_activity.id in status_ids
  39. assert unlisted_activity.id in status_ids
  40. assert private_activity.id in status_ids
  41. refute direct_activity.id in status_ids
  42. end
  43. test "muted emotions", %{user: user, conn: conn} do
  44. other_user = insert(:user)
  45. {:ok, activity} = CommonAPI.post(user, %{status: "."})
  46. {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
  47. User.mute(user, other_user)
  48. result =
  49. conn
  50. |> assign(:user, user)
  51. |> get("/api/v1/timelines/home")
  52. |> json_response_and_validate_schema(200)
  53. assert [
  54. %{
  55. "pleroma" => %{
  56. "emoji_reactions" => []
  57. }
  58. }
  59. ] = result
  60. result =
  61. conn
  62. |> assign(:user, user)
  63. |> get("/api/v1/timelines/home?with_muted=true")
  64. |> json_response_and_validate_schema(200)
  65. assert [
  66. %{
  67. "pleroma" => %{
  68. "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
  69. }
  70. }
  71. ] = result
  72. end
  73. test "filtering", %{conn: conn, user: user} do
  74. local_user = insert(:user)
  75. {:ok, user, local_user} = User.follow(user, local_user)
  76. {:ok, local_activity} = CommonAPI.post(local_user, %{status: "Status"})
  77. with_media = create_with_media_activity(local_user)
  78. remote_user = insert(:user, local: false)
  79. {:ok, _user, remote_user} = User.follow(user, remote_user)
  80. remote_activity = create_remote_activity(remote_user)
  81. without_filter_ids =
  82. conn
  83. |> get("/api/v1/timelines/home")
  84. |> json_response_and_validate_schema(200)
  85. |> Enum.map(& &1["id"])
  86. assert local_activity.id in without_filter_ids
  87. assert remote_activity.id in without_filter_ids
  88. assert with_media.id in without_filter_ids
  89. only_local_ids =
  90. conn
  91. |> get("/api/v1/timelines/home?local=true")
  92. |> json_response_and_validate_schema(200)
  93. |> Enum.map(& &1["id"])
  94. assert local_activity.id in only_local_ids
  95. refute remote_activity.id in only_local_ids
  96. assert with_media.id in only_local_ids
  97. only_local_media_ids =
  98. conn
  99. |> get("/api/v1/timelines/home?local=true&only_media=true")
  100. |> json_response_and_validate_schema(200)
  101. |> Enum.map(& &1["id"])
  102. refute local_activity.id in only_local_media_ids
  103. refute remote_activity.id in only_local_media_ids
  104. assert with_media.id in only_local_media_ids
  105. remote_ids =
  106. conn
  107. |> get("/api/v1/timelines/home?remote=true")
  108. |> json_response_and_validate_schema(200)
  109. |> Enum.map(& &1["id"])
  110. refute local_activity.id in remote_ids
  111. assert remote_activity.id in remote_ids
  112. refute with_media.id in remote_ids
  113. assert conn
  114. |> get("/api/v1/timelines/home?remote=true&only_media=true")
  115. |> json_response_and_validate_schema(200) == []
  116. assert conn
  117. |> get("/api/v1/timelines/home?remote=true&local=true")
  118. |> json_response_and_validate_schema(200) == []
  119. end
  120. end
  121. describe "public" do
  122. @tag capture_log: true
  123. test "the public timeline", %{conn: conn} do
  124. user = insert(:user)
  125. {:ok, activity} = CommonAPI.post(user, %{status: "test"})
  126. with_media = create_with_media_activity(user)
  127. remote = insert(:note_activity, local: false)
  128. assert conn
  129. |> get("/api/v1/timelines/public?local=False")
  130. |> json_response_and_validate_schema(:ok)
  131. |> length == 3
  132. local_ids =
  133. conn
  134. |> get("/api/v1/timelines/public?local=True")
  135. |> json_response_and_validate_schema(:ok)
  136. |> Enum.map(& &1["id"])
  137. assert activity.id in local_ids
  138. assert with_media.id in local_ids
  139. refute remote.id in local_ids
  140. local_ids =
  141. conn
  142. |> get("/api/v1/timelines/public?local=True")
  143. |> json_response_and_validate_schema(:ok)
  144. |> Enum.map(& &1["id"])
  145. assert activity.id in local_ids
  146. assert with_media.id in local_ids
  147. refute remote.id in local_ids
  148. local_ids =
  149. conn
  150. |> get("/api/v1/timelines/public?local=True&only_media=true")
  151. |> json_response_and_validate_schema(:ok)
  152. |> Enum.map(& &1["id"])
  153. refute activity.id in local_ids
  154. assert with_media.id in local_ids
  155. refute remote.id in local_ids
  156. local_ids =
  157. conn
  158. |> get("/api/v1/timelines/public?local=1")
  159. |> json_response_and_validate_schema(:ok)
  160. |> Enum.map(& &1["id"])
  161. assert activity.id in local_ids
  162. assert with_media.id in local_ids
  163. refute remote.id in local_ids
  164. remote_id = remote.id
  165. assert [%{"id" => ^remote_id}] =
  166. conn
  167. |> get("/api/v1/timelines/public?remote=true")
  168. |> json_response_and_validate_schema(:ok)
  169. with_media_id = with_media.id
  170. assert [%{"id" => ^with_media_id}] =
  171. conn
  172. |> get("/api/v1/timelines/public?only_media=true")
  173. |> json_response_and_validate_schema(:ok)
  174. assert conn
  175. |> get("/api/v1/timelines/public?remote=true&only_media=true")
  176. |> json_response_and_validate_schema(:ok) == []
  177. # does not contain repeats
  178. {:ok, _} = CommonAPI.repeat(activity.id, user)
  179. assert [_, _] =
  180. conn
  181. |> get("/api/v1/timelines/public?local=true")
  182. |> json_response_and_validate_schema(:ok)
  183. end
  184. test "the public timeline includes only public statuses for an authenticated user" do
  185. %{user: user, conn: conn} = oauth_access(["read:statuses"])
  186. {:ok, _activity} = CommonAPI.post(user, %{status: "test"})
  187. {:ok, _activity} = CommonAPI.post(user, %{status: "test", visibility: "private"})
  188. {:ok, _activity} = CommonAPI.post(user, %{status: "test", visibility: "unlisted"})
  189. {:ok, _activity} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
  190. res_conn = get(conn, "/api/v1/timelines/public")
  191. assert length(json_response_and_validate_schema(res_conn, 200)) == 1
  192. end
  193. test "doesn't return replies if follower is posting with blocked user" do
  194. %{conn: conn, user: blocker} = oauth_access(["read:statuses"])
  195. [blockee, friend] = insert_list(2, :user)
  196. {:ok, blocker, friend} = User.follow(blocker, friend)
  197. {:ok, _} = User.block(blocker, blockee)
  198. conn = assign(conn, :user, blocker)
  199. {:ok, %{id: activity_id} = activity} = CommonAPI.post(friend, %{status: "hey!"})
  200. {:ok, reply_from_blockee} =
  201. CommonAPI.post(blockee, %{status: "heya", in_reply_to_status_id: activity})
  202. {:ok, _reply_from_friend} =
  203. CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee})
  204. # Still shows replies from yourself
  205. {:ok, %{id: reply_from_me}} =
  206. CommonAPI.post(blocker, %{status: "status", in_reply_to_status_id: reply_from_blockee})
  207. response =
  208. get(conn, "/api/v1/timelines/public")
  209. |> json_response_and_validate_schema(200)
  210. assert length(response) == 2
  211. [%{"id" => ^reply_from_me}, %{"id" => ^activity_id}] = response
  212. end
  213. test "doesn't return posts from users who blocked you when :blockers_visible is disabled" do
  214. clear_config([:activitypub, :blockers_visible], false)
  215. %{conn: conn, user: blockee} = oauth_access(["read:statuses"])
  216. blocker = insert(:user)
  217. {:ok, _} = User.block(blocker, blockee)
  218. conn = assign(conn, :user, blockee)
  219. {:ok, _} = CommonAPI.post(blocker, %{status: "hey!"})
  220. response =
  221. get(conn, "/api/v1/timelines/public")
  222. |> json_response_and_validate_schema(200)
  223. assert length(response) == 0
  224. end
  225. test "doesn't return replies if follow is posting with users from blocked domain" do
  226. %{conn: conn, user: blocker} = oauth_access(["read:statuses"])
  227. friend = insert(:user)
  228. blockee = insert(:user, ap_id: "https://example.com/users/blocked")
  229. {:ok, blocker, friend} = User.follow(blocker, friend)
  230. {:ok, blocker} = User.block_domain(blocker, "example.com")
  231. conn = assign(conn, :user, blocker)
  232. {:ok, %{id: activity_id} = activity} = CommonAPI.post(friend, %{status: "hey!"})
  233. {:ok, reply_from_blockee} =
  234. CommonAPI.post(blockee, %{status: "heya", in_reply_to_status_id: activity})
  235. {:ok, _reply_from_friend} =
  236. CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee})
  237. res_conn = get(conn, "/api/v1/timelines/public")
  238. activities = json_response_and_validate_schema(res_conn, 200)
  239. [%{"id" => ^activity_id}] = activities
  240. end
  241. test "can be filtered by instance", %{conn: conn} do
  242. user = insert(:user, ap_id: "https://lain.com/users/lain")
  243. insert(:note_activity, local: false)
  244. insert(:note_activity, local: false)
  245. {:ok, _} = CommonAPI.post(user, %{status: "test"})
  246. conn = get(conn, "/api/v1/timelines/public?instance=lain.com")
  247. assert length(json_response_and_validate_schema(conn, :ok)) == 1
  248. end
  249. test "muted emotions", %{conn: conn} do
  250. user = insert(:user)
  251. token = insert(:oauth_token, user: user, scopes: ["read:statuses"])
  252. conn =
  253. conn
  254. |> assign(:user, user)
  255. |> assign(:token, token)
  256. other_user = insert(:user)
  257. {:ok, activity} = CommonAPI.post(user, %{status: "."})
  258. {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
  259. User.mute(user, other_user)
  260. result =
  261. conn
  262. |> get("/api/v1/timelines/public")
  263. |> json_response_and_validate_schema(200)
  264. assert [
  265. %{
  266. "pleroma" => %{
  267. "emoji_reactions" => []
  268. }
  269. }
  270. ] = result
  271. result =
  272. conn
  273. |> get("/api/v1/timelines/public?with_muted=true")
  274. |> json_response_and_validate_schema(200)
  275. assert [
  276. %{
  277. "pleroma" => %{
  278. "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
  279. }
  280. }
  281. ] = result
  282. end
  283. test "should return local-only posts for authenticated users" do
  284. user = insert(:user)
  285. %{user: _reader, conn: conn} = oauth_access(["read:statuses"])
  286. {:ok, %{id: id}} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
  287. result =
  288. conn
  289. |> get("/api/v1/timelines/public")
  290. |> json_response_and_validate_schema(200)
  291. assert [%{"id" => ^id}] = result
  292. end
  293. test "should not return local-only posts for users without read:statuses" do
  294. user = insert(:user)
  295. %{user: _reader, conn: conn} = oauth_access([])
  296. {:ok, _activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
  297. result =
  298. conn
  299. |> get("/api/v1/timelines/public")
  300. |> json_response_and_validate_schema(200)
  301. assert [] = result
  302. end
  303. test "should not return local-only posts for anonymous users" do
  304. user = insert(:user)
  305. {:ok, _activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
  306. result =
  307. build_conn()
  308. |> get("/api/v1/timelines/public")
  309. |> json_response_and_validate_schema(200)
  310. assert [] = result
  311. end
  312. end
  313. defp local_and_remote_activities do
  314. insert(:note_activity)
  315. insert(:note_activity, local: false)
  316. :ok
  317. end
  318. describe "public with restrict unauthenticated timeline for local and federated timelines" do
  319. setup do: local_and_remote_activities()
  320. setup do: clear_config([:restrict_unauthenticated, :timelines, :local], true)
  321. setup do: clear_config([:restrict_unauthenticated, :timelines, :federated], true)
  322. test "if user is unauthenticated", %{conn: conn} do
  323. res_conn = get(conn, "/api/v1/timelines/public?local=true")
  324. assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
  325. "error" => "authorization required for timeline view"
  326. }
  327. res_conn = get(conn, "/api/v1/timelines/public?local=false")
  328. assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
  329. "error" => "authorization required for timeline view"
  330. }
  331. end
  332. test "if user is authenticated" do
  333. %{conn: conn} = oauth_access(["read:statuses"])
  334. res_conn = get(conn, "/api/v1/timelines/public?local=true")
  335. assert length(json_response_and_validate_schema(res_conn, 200)) == 1
  336. res_conn = get(conn, "/api/v1/timelines/public?local=false")
  337. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  338. end
  339. end
  340. describe "public with restrict unauthenticated timeline for local" do
  341. setup do: local_and_remote_activities()
  342. setup do: clear_config([:restrict_unauthenticated, :timelines, :local], true)
  343. test "if user is unauthenticated", %{conn: conn} do
  344. res_conn = get(conn, "/api/v1/timelines/public?local=true")
  345. assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
  346. "error" => "authorization required for timeline view"
  347. }
  348. res_conn = get(conn, "/api/v1/timelines/public?local=false")
  349. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  350. end
  351. test "if user is authenticated", %{conn: _conn} do
  352. %{conn: conn} = oauth_access(["read:statuses"])
  353. res_conn = get(conn, "/api/v1/timelines/public?local=true")
  354. assert length(json_response_and_validate_schema(res_conn, 200)) == 1
  355. res_conn = get(conn, "/api/v1/timelines/public?local=false")
  356. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  357. end
  358. end
  359. describe "public with restrict unauthenticated timeline for remote" do
  360. setup do: local_and_remote_activities()
  361. setup do: clear_config([:restrict_unauthenticated, :timelines, :federated], true)
  362. test "if user is unauthenticated", %{conn: conn} do
  363. res_conn = get(conn, "/api/v1/timelines/public?local=true")
  364. assert length(json_response_and_validate_schema(res_conn, 200)) == 1
  365. res_conn = get(conn, "/api/v1/timelines/public?local=false")
  366. assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
  367. "error" => "authorization required for timeline view"
  368. }
  369. end
  370. test "if user is authenticated", %{conn: _conn} do
  371. %{conn: conn} = oauth_access(["read:statuses"])
  372. res_conn = get(conn, "/api/v1/timelines/public?local=true")
  373. assert length(json_response_and_validate_schema(res_conn, 200)) == 1
  374. res_conn = get(conn, "/api/v1/timelines/public?local=false")
  375. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  376. end
  377. end
  378. describe "direct" do
  379. test "direct timeline", %{conn: conn} do
  380. user_one = insert(:user)
  381. user_two = insert(:user)
  382. {:ok, user_two, user_one} = User.follow(user_two, user_one)
  383. {:ok, direct} =
  384. CommonAPI.post(user_one, %{
  385. status: "Hi @#{user_two.nickname}!",
  386. visibility: "direct"
  387. })
  388. {:ok, _follower_only} =
  389. CommonAPI.post(user_one, %{
  390. status: "Hi @#{user_two.nickname}!",
  391. visibility: "private"
  392. })
  393. conn_user_two =
  394. conn
  395. |> assign(:user, user_two)
  396. |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
  397. # Only direct should be visible here
  398. res_conn = get(conn_user_two, "/api/v1/timelines/direct")
  399. assert [status] = json_response_and_validate_schema(res_conn, :ok)
  400. assert %{"visibility" => "direct"} = status
  401. assert status["url"] != direct.data["id"]
  402. # User should be able to see their own direct message
  403. res_conn =
  404. build_conn()
  405. |> assign(:user, user_one)
  406. |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"]))
  407. |> get("/api/v1/timelines/direct")
  408. [status] = json_response_and_validate_schema(res_conn, :ok)
  409. assert %{"visibility" => "direct"} = status
  410. # Both should be visible here
  411. res_conn = get(conn_user_two, "/api/v1/timelines/home")
  412. [_s1, _s2] = json_response_and_validate_schema(res_conn, :ok)
  413. # Test pagination
  414. Enum.each(1..20, fn _ ->
  415. {:ok, _} =
  416. CommonAPI.post(user_one, %{
  417. status: "Hi @#{user_two.nickname}!",
  418. visibility: "direct"
  419. })
  420. end)
  421. res_conn = get(conn_user_two, "/api/v1/timelines/direct")
  422. statuses = json_response_and_validate_schema(res_conn, :ok)
  423. assert length(statuses) == 20
  424. max_id = List.last(statuses)["id"]
  425. res_conn = get(conn_user_two, "/api/v1/timelines/direct?max_id=#{max_id}")
  426. assert [status] = json_response_and_validate_schema(res_conn, :ok)
  427. assert status["url"] != direct.data["id"]
  428. end
  429. test "doesn't include DMs from blocked users" do
  430. %{user: blocker, conn: conn} = oauth_access(["read:statuses"])
  431. blocked = insert(:user)
  432. other_user = insert(:user)
  433. {:ok, _user_relationship} = User.block(blocker, blocked)
  434. {:ok, _blocked_direct} =
  435. CommonAPI.post(blocked, %{
  436. status: "Hi @#{blocker.nickname}!",
  437. visibility: "direct"
  438. })
  439. {:ok, direct} =
  440. CommonAPI.post(other_user, %{
  441. status: "Hi @#{blocker.nickname}!",
  442. visibility: "direct"
  443. })
  444. res_conn = get(conn, "/api/v1/timelines/direct")
  445. [status] = json_response_and_validate_schema(res_conn, :ok)
  446. assert status["id"] == direct.id
  447. end
  448. end
  449. describe "list" do
  450. setup do: oauth_access(["read:lists"])
  451. test "does not contain retoots", %{user: user, conn: conn} do
  452. other_user = insert(:user)
  453. {:ok, activity_one} = CommonAPI.post(user, %{status: "Marisa is cute."})
  454. {:ok, activity_two} = CommonAPI.post(other_user, %{status: "Marisa is stupid."})
  455. {:ok, _} = CommonAPI.repeat(activity_one.id, other_user)
  456. {:ok, list} = Pleroma.List.create("name", user)
  457. {:ok, list} = Pleroma.List.follow(list, other_user)
  458. conn = get(conn, "/api/v1/timelines/list/#{list.id}")
  459. assert [%{"id" => id}] = json_response_and_validate_schema(conn, :ok)
  460. assert id == to_string(activity_two.id)
  461. end
  462. test "works with pagination", %{user: user, conn: conn} do
  463. other_user = insert(:user)
  464. {:ok, list} = Pleroma.List.create("name", user)
  465. {:ok, list} = Pleroma.List.follow(list, other_user)
  466. Enum.each(1..30, fn i ->
  467. CommonAPI.post(other_user, %{status: "post number #{i}"})
  468. end)
  469. res =
  470. get(conn, "/api/v1/timelines/list/#{list.id}?limit=1")
  471. |> json_response_and_validate_schema(:ok)
  472. assert length(res) == 1
  473. [first] = res
  474. res =
  475. get(conn, "/api/v1/timelines/list/#{list.id}?max_id=#{first["id"]}&limit=30")
  476. |> json_response_and_validate_schema(:ok)
  477. assert length(res) == 29
  478. end
  479. test "list timeline", %{user: user, conn: conn} do
  480. other_user = insert(:user)
  481. {:ok, _activity_one} = CommonAPI.post(user, %{status: "Marisa is cute."})
  482. {:ok, activity_two} = CommonAPI.post(other_user, %{status: "Marisa is cute."})
  483. {:ok, list} = Pleroma.List.create("name", user)
  484. {:ok, list} = Pleroma.List.follow(list, other_user)
  485. conn = get(conn, "/api/v1/timelines/list/#{list.id}")
  486. assert [%{"id" => id}] = json_response_and_validate_schema(conn, :ok)
  487. assert id == to_string(activity_two.id)
  488. end
  489. test "list timeline does not leak non-public statuses for unfollowed users", %{
  490. user: user,
  491. conn: conn
  492. } do
  493. other_user = insert(:user)
  494. {:ok, activity_one} = CommonAPI.post(other_user, %{status: "Marisa is cute."})
  495. {:ok, _activity_two} =
  496. CommonAPI.post(other_user, %{
  497. status: "Marisa is cute.",
  498. visibility: "private"
  499. })
  500. {:ok, list} = Pleroma.List.create("name", user)
  501. {:ok, list} = Pleroma.List.follow(list, other_user)
  502. conn = get(conn, "/api/v1/timelines/list/#{list.id}")
  503. assert [%{"id" => id}] = json_response_and_validate_schema(conn, :ok)
  504. assert id == to_string(activity_one.id)
  505. end
  506. test "muted emotions", %{user: user, conn: conn} do
  507. user2 = insert(:user)
  508. user3 = insert(:user)
  509. {:ok, activity} = CommonAPI.post(user2, %{status: "."})
  510. {:ok, _} = CommonAPI.react_with_emoji(activity.id, user3, "🎅")
  511. User.mute(user, user3)
  512. {:ok, list} = Pleroma.List.create("name", user)
  513. {:ok, list} = Pleroma.List.follow(list, user2)
  514. result =
  515. conn
  516. |> get("/api/v1/timelines/list/#{list.id}")
  517. |> json_response_and_validate_schema(200)
  518. assert [
  519. %{
  520. "pleroma" => %{
  521. "emoji_reactions" => []
  522. }
  523. }
  524. ] = result
  525. result =
  526. conn
  527. |> get("/api/v1/timelines/list/#{list.id}?with_muted=true")
  528. |> json_response_and_validate_schema(200)
  529. assert [
  530. %{
  531. "pleroma" => %{
  532. "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
  533. }
  534. }
  535. ] = result
  536. end
  537. test "filtering", %{user: user, conn: conn} do
  538. {:ok, list} = Pleroma.List.create("name", user)
  539. local_user = insert(:user)
  540. {:ok, local_activity} = CommonAPI.post(local_user, %{status: "Marisa is stupid."})
  541. with_media = create_with_media_activity(local_user)
  542. {:ok, list} = Pleroma.List.follow(list, local_user)
  543. remote_user = insert(:user, local: false)
  544. remote_activity = create_remote_activity(remote_user)
  545. {:ok, list} = Pleroma.List.follow(list, remote_user)
  546. all_ids =
  547. conn
  548. |> get("/api/v1/timelines/list/#{list.id}")
  549. |> json_response_and_validate_schema(200)
  550. |> Enum.map(& &1["id"])
  551. assert local_activity.id in all_ids
  552. assert with_media.id in all_ids
  553. assert remote_activity.id in all_ids
  554. only_local_ids =
  555. conn
  556. |> get("/api/v1/timelines/list/#{list.id}?local=true")
  557. |> json_response_and_validate_schema(200)
  558. |> Enum.map(& &1["id"])
  559. assert local_activity.id in only_local_ids
  560. assert with_media.id in only_local_ids
  561. refute remote_activity.id in only_local_ids
  562. only_local_media_ids =
  563. conn
  564. |> get("/api/v1/timelines/list/#{list.id}?local=true&only_media=true")
  565. |> json_response_and_validate_schema(200)
  566. |> Enum.map(& &1["id"])
  567. refute local_activity.id in only_local_media_ids
  568. assert with_media.id in only_local_media_ids
  569. refute remote_activity.id in only_local_media_ids
  570. remote_ids =
  571. conn
  572. |> get("/api/v1/timelines/list/#{list.id}?remote=true")
  573. |> json_response_and_validate_schema(200)
  574. |> Enum.map(& &1["id"])
  575. refute local_activity.id in remote_ids
  576. refute with_media.id in remote_ids
  577. assert remote_activity.id in remote_ids
  578. assert conn
  579. |> get("/api/v1/timelines/list/#{list.id}?remote=true&only_media=true")
  580. |> json_response_and_validate_schema(200) == []
  581. only_media_ids =
  582. conn
  583. |> get("/api/v1/timelines/list/#{list.id}?only_media=true")
  584. |> json_response_and_validate_schema(200)
  585. |> Enum.map(& &1["id"])
  586. refute local_activity.id in only_media_ids
  587. assert with_media.id in only_media_ids
  588. refute remote_activity.id in only_media_ids
  589. assert conn
  590. |> get("/api/v1/timelines/list/#{list.id}?only_media=true&local=true&remote=true")
  591. |> json_response_and_validate_schema(200) == []
  592. end
  593. end
  594. describe "hashtag" do
  595. setup do: oauth_access(["n/a"])
  596. @tag capture_log: true
  597. test "hashtag timeline", %{conn: conn} do
  598. following = insert(:user)
  599. {:ok, activity} = CommonAPI.post(following, %{status: "test #2hu"})
  600. with_media = create_with_media_activity(following)
  601. remote = insert(:user, local: false)
  602. remote_activity = create_remote_activity(remote)
  603. all_ids =
  604. conn
  605. |> get("/api/v1/timelines/tag/2hu")
  606. |> json_response_and_validate_schema(:ok)
  607. |> Enum.map(& &1["id"])
  608. assert activity.id in all_ids
  609. assert with_media.id in all_ids
  610. assert remote_activity.id in all_ids
  611. # works for different capitalization too
  612. all_ids =
  613. conn
  614. |> get("/api/v1/timelines/tag/2HU")
  615. |> json_response_and_validate_schema(:ok)
  616. |> Enum.map(& &1["id"])
  617. assert activity.id in all_ids
  618. assert with_media.id in all_ids
  619. assert remote_activity.id in all_ids
  620. local_ids =
  621. conn
  622. |> get("/api/v1/timelines/tag/2hu?local=true")
  623. |> json_response_and_validate_schema(:ok)
  624. |> Enum.map(& &1["id"])
  625. assert activity.id in local_ids
  626. assert with_media.id in local_ids
  627. refute remote_activity.id in local_ids
  628. remote_ids =
  629. conn
  630. |> get("/api/v1/timelines/tag/2hu?remote=true")
  631. |> json_response_and_validate_schema(:ok)
  632. |> Enum.map(& &1["id"])
  633. refute activity.id in remote_ids
  634. refute with_media.id in remote_ids
  635. assert remote_activity.id in remote_ids
  636. media_ids =
  637. conn
  638. |> get("/api/v1/timelines/tag/2hu?only_media=true")
  639. |> json_response_and_validate_schema(:ok)
  640. |> Enum.map(& &1["id"])
  641. refute activity.id in media_ids
  642. assert with_media.id in media_ids
  643. refute remote_activity.id in media_ids
  644. media_local_ids =
  645. conn
  646. |> get("/api/v1/timelines/tag/2hu?only_media=true&local=true")
  647. |> json_response_and_validate_schema(:ok)
  648. |> Enum.map(& &1["id"])
  649. refute activity.id in media_local_ids
  650. assert with_media.id in media_local_ids
  651. refute remote_activity.id in media_local_ids
  652. ids =
  653. conn
  654. |> get("/api/v1/timelines/tag/2hu?only_media=true&local=true&remote=true")
  655. |> json_response_and_validate_schema(:ok)
  656. |> Enum.map(& &1["id"])
  657. refute activity.id in ids
  658. refute with_media.id in ids
  659. refute remote_activity.id in ids
  660. assert conn
  661. |> get("/api/v1/timelines/tag/2hu?only_media=true&remote=true")
  662. |> json_response_and_validate_schema(:ok) == []
  663. end
  664. test "multi-hashtag timeline", %{conn: conn} do
  665. user = insert(:user)
  666. {:ok, activity_test} = CommonAPI.post(user, %{status: "#test"})
  667. {:ok, activity_test1} = CommonAPI.post(user, %{status: "#test #test1"})
  668. {:ok, activity_none} = CommonAPI.post(user, %{status: "#test #none"})
  669. any_test = get(conn, "/api/v1/timelines/tag/test?any[]=test1")
  670. [status_none, status_test1, status_test] = json_response_and_validate_schema(any_test, :ok)
  671. assert to_string(activity_test.id) == status_test["id"]
  672. assert to_string(activity_test1.id) == status_test1["id"]
  673. assert to_string(activity_none.id) == status_none["id"]
  674. restricted_test = get(conn, "/api/v1/timelines/tag/test?all[]=test1&none[]=none")
  675. assert [status_test1] == json_response_and_validate_schema(restricted_test, :ok)
  676. all_test = get(conn, "/api/v1/timelines/tag/test?all[]=none")
  677. assert [status_none] == json_response_and_validate_schema(all_test, :ok)
  678. end
  679. test "muted emotions", %{conn: conn} do
  680. user = insert(:user)
  681. token = insert(:oauth_token, user: user, scopes: ["read:statuses"])
  682. conn =
  683. conn
  684. |> assign(:user, user)
  685. |> assign(:token, token)
  686. other_user = insert(:user)
  687. {:ok, activity} = CommonAPI.post(user, %{status: "test #2hu"})
  688. {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
  689. User.mute(user, other_user)
  690. result =
  691. conn
  692. |> get("/api/v1/timelines/tag/2hu")
  693. |> json_response_and_validate_schema(200)
  694. assert [
  695. %{
  696. "pleroma" => %{
  697. "emoji_reactions" => []
  698. }
  699. }
  700. ] = result
  701. result =
  702. conn
  703. |> get("/api/v1/timelines/tag/2hu?with_muted=true")
  704. |> json_response_and_validate_schema(200)
  705. assert [
  706. %{
  707. "pleroma" => %{
  708. "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
  709. }
  710. }
  711. ] = result
  712. end
  713. end
  714. describe "hashtag timeline handling of restrict_unauthenticated setting" do
  715. setup do
  716. user = insert(:user)
  717. {:ok, activity1} = CommonAPI.post(user, %{status: "test #tag1"})
  718. {:ok, _activity2} = CommonAPI.post(user, %{status: "test #tag1"})
  719. activity1
  720. |> Ecto.Changeset.change(%{local: false})
  721. |> Pleroma.Repo.update()
  722. base_uri = "/api/v1/timelines/tag/tag1"
  723. error_response = %{"error" => "authorization required for timeline view"}
  724. %{base_uri: base_uri, error_response: error_response}
  725. end
  726. defp ensure_authenticated_access(base_uri) do
  727. %{conn: auth_conn} = oauth_access(["read:statuses"])
  728. res_conn = get(auth_conn, "#{base_uri}?local=true")
  729. assert length(json_response_and_validate_schema(res_conn, 200)) == 1
  730. res_conn = get(auth_conn, "#{base_uri}?local=false")
  731. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  732. end
  733. test "with default settings on private instances, returns 403 for unauthenticated users", %{
  734. conn: conn,
  735. base_uri: base_uri,
  736. error_response: error_response
  737. } do
  738. clear_config([:instance, :public], false)
  739. clear_config([:restrict_unauthenticated, :timelines])
  740. for local <- [true, false] do
  741. res_conn = get(conn, "#{base_uri}?local=#{local}")
  742. assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
  743. end
  744. ensure_authenticated_access(base_uri)
  745. end
  746. test "with `%{local: true, federated: true}`, returns 403 for unauthenticated users", %{
  747. conn: conn,
  748. base_uri: base_uri,
  749. error_response: error_response
  750. } do
  751. clear_config([:restrict_unauthenticated, :timelines, :local], true)
  752. clear_config([:restrict_unauthenticated, :timelines, :federated], true)
  753. for local <- [true, false] do
  754. res_conn = get(conn, "#{base_uri}?local=#{local}")
  755. assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
  756. end
  757. ensure_authenticated_access(base_uri)
  758. end
  759. test "with `%{local: false, federated: true}`, forbids unauthenticated access to federated timeline",
  760. %{conn: conn, base_uri: base_uri, error_response: error_response} do
  761. clear_config([:restrict_unauthenticated, :timelines, :local], false)
  762. clear_config([:restrict_unauthenticated, :timelines, :federated], true)
  763. res_conn = get(conn, "#{base_uri}?local=true")
  764. assert length(json_response_and_validate_schema(res_conn, 200)) == 1
  765. res_conn = get(conn, "#{base_uri}?local=false")
  766. assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
  767. ensure_authenticated_access(base_uri)
  768. end
  769. test "with `%{local: true, federated: false}`, forbids unauthenticated access to public timeline" <>
  770. "(but not to local public activities which are delivered as part of federated timeline)",
  771. %{conn: conn, base_uri: base_uri, error_response: error_response} do
  772. clear_config([:restrict_unauthenticated, :timelines, :local], true)
  773. clear_config([:restrict_unauthenticated, :timelines, :federated], false)
  774. res_conn = get(conn, "#{base_uri}?local=true")
  775. assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
  776. # Note: local activities get delivered as part of federated timeline
  777. res_conn = get(conn, "#{base_uri}?local=false")
  778. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  779. ensure_authenticated_access(base_uri)
  780. end
  781. end
  782. defp create_remote_activity(user) do
  783. obj =
  784. insert(:note, %{
  785. data: %{
  786. "to" => [
  787. "https://www.w3.org/ns/activitystreams#Public",
  788. User.ap_followers(user)
  789. ]
  790. },
  791. user: user
  792. })
  793. insert(:note_activity, %{
  794. note: obj,
  795. recipients: [
  796. "https://www.w3.org/ns/activitystreams#Public",
  797. User.ap_followers(user)
  798. ],
  799. user: user,
  800. local: false
  801. })
  802. end
  803. defp create_with_media_activity(user) do
  804. obj = insert(:attachment_note, user: user)
  805. insert(:note_activity, %{
  806. note: obj,
  807. recipients: ["https://www.w3.org/ns/activitystreams#Public", User.ap_followers(user)],
  808. user: user
  809. })
  810. end
  811. end