logo

pleroma

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

timeline_controller_test.exs (34870B)


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