1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
6 use Pleroma.Web.ConnCase
13 alias Pleroma.Web.CommonAPI
16 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
21 setup do: oauth_access(["read:statuses"])
23 test "the home timeline", %{user: user, conn: conn} do
24 following = insert(:user, nickname: "followed")
25 third_user = insert(:user, nickname: "repeated")
27 {:ok, _activity} = CommonAPI.post(following, %{"status" => "post"})
28 {:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"})
29 {:ok, _, _} = CommonAPI.repeat(activity.id, following)
31 ret_conn = get(conn, "/api/v1/timelines/home")
33 assert Enum.empty?(json_response(ret_conn, :ok))
35 {:ok, _user} = User.follow(user, following)
37 ret_conn = get(conn, "/api/v1/timelines/home")
42 "content" => "repeated post",
45 "relationship" => %{"following" => false, "followed_by" => false}
49 "account" => %{"pleroma" => %{"relationship" => %{"following" => true}}}
55 "pleroma" => %{"relationship" => %{"following" => true}}
58 ] = json_response(ret_conn, :ok)
60 {:ok, _user} = User.follow(third_user, user)
62 ret_conn = get(conn, "/api/v1/timelines/home")
67 "content" => "repeated post",
71 # This part does not match correctly
72 "relationship" => %{"following" => false, "followed_by" => true}
76 "account" => %{"pleroma" => %{"relationship" => %{"following" => true}}}
82 "pleroma" => %{"relationship" => %{"following" => true}}
85 ] = json_response(ret_conn, :ok)
88 test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do
89 {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
90 {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
92 {:ok, unlisted_activity} =
93 CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
95 {:ok, private_activity} =
96 CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
98 conn = get(conn, "/api/v1/timelines/home", %{"exclude_visibilities" => ["direct"]})
100 assert status_ids = json_response(conn, :ok) |> Enum.map(& &1["id"])
101 assert public_activity.id in status_ids
102 assert unlisted_activity.id in status_ids
103 assert private_activity.id in status_ids
104 refute direct_activity.id in status_ids
109 @tag capture_log: true
110 test "the public timeline", %{conn: conn} do
111 following = insert(:user)
113 {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
115 _activity = insert(:note_activity, local: false)
117 conn = get(conn, "/api/v1/timelines/public", %{"local" => "False"})
119 assert length(json_response(conn, :ok)) == 2
121 conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "True"})
123 assert [%{"content" => "test"}] = json_response(conn, :ok)
125 conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "1"})
127 assert [%{"content" => "test"}] = json_response(conn, :ok)
130 test "the public timeline includes only public statuses for an authenticated user" do
131 %{user: user, conn: conn} = oauth_access(["read:statuses"])
133 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test"})
134 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"})
135 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"})
136 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
138 res_conn = get(conn, "/api/v1/timelines/public")
139 assert length(json_response(res_conn, 200)) == 1
143 defp local_and_remote_activities do
144 insert(:note_activity)
145 insert(:note_activity, local: false)
149 describe "public with restrict unauthenticated timeline for local and federated timelines" do
150 setup do: local_and_remote_activities()
152 setup do: clear_config([:restrict_unauthenticated, :timelines, :local], true)
154 setup do: clear_config([:restrict_unauthenticated, :timelines, :federated], true)
156 test "if user is unauthenticated", %{conn: conn} do
157 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
159 assert json_response(res_conn, :unauthorized) == %{
160 "error" => "authorization required for timeline view"
163 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
165 assert json_response(res_conn, :unauthorized) == %{
166 "error" => "authorization required for timeline view"
170 test "if user is authenticated" do
171 %{conn: conn} = oauth_access(["read:statuses"])
173 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
174 assert length(json_response(res_conn, 200)) == 1
176 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
177 assert length(json_response(res_conn, 200)) == 2
181 describe "public with restrict unauthenticated timeline for local" do
182 setup do: local_and_remote_activities()
184 setup do: clear_config([:restrict_unauthenticated, :timelines, :local], true)
186 test "if user is unauthenticated", %{conn: conn} do
187 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
189 assert json_response(res_conn, :unauthorized) == %{
190 "error" => "authorization required for timeline view"
193 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
194 assert length(json_response(res_conn, 200)) == 2
197 test "if user is authenticated", %{conn: _conn} do
198 %{conn: conn} = oauth_access(["read:statuses"])
200 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
201 assert length(json_response(res_conn, 200)) == 1
203 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
204 assert length(json_response(res_conn, 200)) == 2
208 describe "public with restrict unauthenticated timeline for remote" do
209 setup do: local_and_remote_activities()
211 setup do: clear_config([:restrict_unauthenticated, :timelines, :federated], true)
213 test "if user is unauthenticated", %{conn: conn} do
214 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
215 assert length(json_response(res_conn, 200)) == 1
217 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
219 assert json_response(res_conn, :unauthorized) == %{
220 "error" => "authorization required for timeline view"
224 test "if user is authenticated", %{conn: _conn} do
225 %{conn: conn} = oauth_access(["read:statuses"])
227 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
228 assert length(json_response(res_conn, 200)) == 1
230 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
231 assert length(json_response(res_conn, 200)) == 2
236 test "direct timeline", %{conn: conn} do
237 user_one = insert(:user)
238 user_two = insert(:user)
240 {:ok, user_two} = User.follow(user_two, user_one)
243 CommonAPI.post(user_one, %{
244 "status" => "Hi @#{user_two.nickname}!",
245 "visibility" => "direct"
248 {:ok, _follower_only} =
249 CommonAPI.post(user_one, %{
250 "status" => "Hi @#{user_two.nickname}!",
251 "visibility" => "private"
256 |> assign(:user, user_two)
257 |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
259 # Only direct should be visible here
260 res_conn = get(conn_user_two, "api/v1/timelines/direct")
262 [status] = json_response(res_conn, :ok)
264 assert %{"visibility" => "direct"} = status
265 assert status["url"] != direct.data["id"]
267 # User should be able to see their own direct message
270 |> assign(:user, user_one)
271 |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"]))
272 |> get("api/v1/timelines/direct")
274 [status] = json_response(res_conn, :ok)
276 assert %{"visibility" => "direct"} = status
278 # Both should be visible here
279 res_conn = get(conn_user_two, "api/v1/timelines/home")
281 [_s1, _s2] = json_response(res_conn, :ok)
284 Enum.each(1..20, fn _ ->
286 CommonAPI.post(user_one, %{
287 "status" => "Hi @#{user_two.nickname}!",
288 "visibility" => "direct"
292 res_conn = get(conn_user_two, "api/v1/timelines/direct")
294 statuses = json_response(res_conn, :ok)
295 assert length(statuses) == 20
298 get(conn_user_two, "api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
300 [status] = json_response(res_conn, :ok)
302 assert status["url"] != direct.data["id"]
305 test "doesn't include DMs from blocked users" do
306 %{user: blocker, conn: conn} = oauth_access(["read:statuses"])
307 blocked = insert(:user)
308 other_user = insert(:user)
309 {:ok, _user_relationship} = User.block(blocker, blocked)
311 {:ok, _blocked_direct} =
312 CommonAPI.post(blocked, %{
313 "status" => "Hi @#{blocker.nickname}!",
314 "visibility" => "direct"
318 CommonAPI.post(other_user, %{
319 "status" => "Hi @#{blocker.nickname}!",
320 "visibility" => "direct"
323 res_conn = get(conn, "api/v1/timelines/direct")
325 [status] = json_response(res_conn, :ok)
326 assert status["id"] == direct.id
331 setup do: oauth_access(["read:lists"])
333 test "list timeline", %{user: user, conn: conn} do
334 other_user = insert(:user)
335 {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."})
336 {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
337 {:ok, list} = Pleroma.List.create("name", user)
338 {:ok, list} = Pleroma.List.follow(list, other_user)
340 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
342 assert [%{"id" => id}] = json_response(conn, :ok)
344 assert id == to_string(activity_two.id)
347 test "list timeline does not leak non-public statuses for unfollowed users", %{
351 other_user = insert(:user)
352 {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
354 {:ok, _activity_two} =
355 CommonAPI.post(other_user, %{
356 "status" => "Marisa is cute.",
357 "visibility" => "private"
360 {:ok, list} = Pleroma.List.create("name", user)
361 {:ok, list} = Pleroma.List.follow(list, other_user)
363 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
365 assert [%{"id" => id}] = json_response(conn, :ok)
367 assert id == to_string(activity_one.id)
371 describe "hashtag" do
372 setup do: oauth_access(["n/a"])
374 @tag capture_log: true
375 test "hashtag timeline", %{conn: conn} do
376 following = insert(:user)
378 {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"})
380 nconn = get(conn, "/api/v1/timelines/tag/2hu")
382 assert [%{"id" => id}] = json_response(nconn, :ok)
384 assert id == to_string(activity.id)
386 # works for different capitalization too
387 nconn = get(conn, "/api/v1/timelines/tag/2HU")
389 assert [%{"id" => id}] = json_response(nconn, :ok)
391 assert id == to_string(activity.id)
394 test "multi-hashtag timeline", %{conn: conn} do
397 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
398 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
399 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
401 any_test = get(conn, "/api/v1/timelines/tag/test", %{"any" => ["test1"]})
403 [status_none, status_test1, status_test] = json_response(any_test, :ok)
405 assert to_string(activity_test.id) == status_test["id"]
406 assert to_string(activity_test1.id) == status_test1["id"]
407 assert to_string(activity_none.id) == status_none["id"]
410 get(conn, "/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
412 assert [status_test1] == json_response(restricted_test, :ok)
414 all_test = get(conn, "/api/v1/timelines/tag/test", %{"all" => ["none"]})
416 assert [status_none] == json_response(all_test, :ok)