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)
26 {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
28 ret_conn = get(conn, "/api/v1/timelines/home")
30 assert Enum.empty?(json_response(ret_conn, :ok))
32 {:ok, _user} = User.follow(user, following)
34 conn = get(conn, "/api/v1/timelines/home")
36 assert [%{"content" => "test"}] = json_response(conn, :ok)
39 test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do
40 {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
41 {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
43 {:ok, unlisted_activity} =
44 CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
46 {:ok, private_activity} =
47 CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
49 conn = get(conn, "/api/v1/timelines/home", %{"exclude_visibilities" => ["direct"]})
51 assert status_ids = json_response(conn, :ok) |> Enum.map(& &1["id"])
52 assert public_activity.id in status_ids
53 assert unlisted_activity.id in status_ids
54 assert private_activity.id in status_ids
55 refute direct_activity.id in status_ids
60 @tag capture_log: true
61 test "the public timeline", %{conn: conn} do
62 following = insert(:user)
64 {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
66 _activity = insert(:note_activity, local: false)
68 conn = get(conn, "/api/v1/timelines/public", %{"local" => "False"})
70 assert length(json_response(conn, :ok)) == 2
72 conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "True"})
74 assert [%{"content" => "test"}] = json_response(conn, :ok)
76 conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "1"})
78 assert [%{"content" => "test"}] = json_response(conn, :ok)
81 test "the public timeline includes only public statuses for an authenticated user" do
82 %{user: user, conn: conn} = oauth_access(["read:statuses"])
84 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test"})
85 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"})
86 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"})
87 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
89 res_conn = get(conn, "/api/v1/timelines/public")
90 assert length(json_response(res_conn, 200)) == 1
94 defp local_and_remote_activities do
95 insert(:note_activity)
96 insert(:note_activity, local: false)
100 describe "public with restrict unauthenticated timeline for local and federated timelines" do
101 setup do: local_and_remote_activities()
103 clear_config([:restrict_unauthenticated, :timelines, :local]) do
104 Config.put([:restrict_unauthenticated, :timelines, :local], true)
107 clear_config([:restrict_unauthenticated, :timelines, :federated]) do
108 Config.put([:restrict_unauthenticated, :timelines, :federated], true)
111 test "if user is unauthenticated", %{conn: conn} do
112 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
114 assert json_response(res_conn, :unauthorized) == %{
115 "error" => "authorization required for timeline view"
118 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
120 assert json_response(res_conn, :unauthorized) == %{
121 "error" => "authorization required for timeline view"
125 test "if user is authenticated" do
126 %{conn: conn} = oauth_access(["read:statuses"])
128 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
129 assert length(json_response(res_conn, 200)) == 1
131 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
132 assert length(json_response(res_conn, 200)) == 2
136 describe "public with restrict unauthenticated timeline for local" do
137 setup do: local_and_remote_activities()
139 clear_config([:restrict_unauthenticated, :timelines, :local]) do
140 Config.put([:restrict_unauthenticated, :timelines, :local], true)
143 test "if user is unauthenticated", %{conn: conn} do
144 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
146 assert json_response(res_conn, :unauthorized) == %{
147 "error" => "authorization required for timeline view"
150 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
151 assert length(json_response(res_conn, 200)) == 2
154 test "if user is authenticated", %{conn: _conn} do
155 %{conn: conn} = oauth_access(["read:statuses"])
157 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
158 assert length(json_response(res_conn, 200)) == 1
160 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
161 assert length(json_response(res_conn, 200)) == 2
165 describe "public with restrict unauthenticated timeline for remote" do
166 setup do: local_and_remote_activities()
168 clear_config([:restrict_unauthenticated, :timelines, :federated]) do
169 Config.put([:restrict_unauthenticated, :timelines, :federated], true)
172 test "if user is unauthenticated", %{conn: conn} do
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"})
178 assert json_response(res_conn, :unauthorized) == %{
179 "error" => "authorization required for timeline view"
183 test "if user is authenticated", %{conn: _conn} do
184 %{conn: conn} = oauth_access(["read:statuses"])
186 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
187 assert length(json_response(res_conn, 200)) == 1
189 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
190 assert length(json_response(res_conn, 200)) == 2
195 test "direct timeline", %{conn: conn} do
196 user_one = insert(:user)
197 user_two = insert(:user)
199 {:ok, user_two} = User.follow(user_two, user_one)
202 CommonAPI.post(user_one, %{
203 "status" => "Hi @#{user_two.nickname}!",
204 "visibility" => "direct"
207 {:ok, _follower_only} =
208 CommonAPI.post(user_one, %{
209 "status" => "Hi @#{user_two.nickname}!",
210 "visibility" => "private"
215 |> assign(:user, user_two)
216 |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
218 # Only direct should be visible here
219 res_conn = get(conn_user_two, "api/v1/timelines/direct")
221 [status] = json_response(res_conn, :ok)
223 assert %{"visibility" => "direct"} = status
224 assert status["url"] != direct.data["id"]
226 # User should be able to see their own direct message
229 |> assign(:user, user_one)
230 |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"]))
231 |> get("api/v1/timelines/direct")
233 [status] = json_response(res_conn, :ok)
235 assert %{"visibility" => "direct"} = status
237 # Both should be visible here
238 res_conn = get(conn_user_two, "api/v1/timelines/home")
240 [_s1, _s2] = json_response(res_conn, :ok)
243 Enum.each(1..20, fn _ ->
245 CommonAPI.post(user_one, %{
246 "status" => "Hi @#{user_two.nickname}!",
247 "visibility" => "direct"
251 res_conn = get(conn_user_two, "api/v1/timelines/direct")
253 statuses = json_response(res_conn, :ok)
254 assert length(statuses) == 20
257 get(conn_user_two, "api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
259 [status] = json_response(res_conn, :ok)
261 assert status["url"] != direct.data["id"]
264 test "doesn't include DMs from blocked users" do
265 %{user: blocker, conn: conn} = oauth_access(["read:statuses"])
266 blocked = insert(:user)
267 other_user = insert(:user)
268 {:ok, _user_relationship} = User.block(blocker, blocked)
270 {:ok, _blocked_direct} =
271 CommonAPI.post(blocked, %{
272 "status" => "Hi @#{blocker.nickname}!",
273 "visibility" => "direct"
277 CommonAPI.post(other_user, %{
278 "status" => "Hi @#{blocker.nickname}!",
279 "visibility" => "direct"
282 res_conn = get(conn, "api/v1/timelines/direct")
284 [status] = json_response(res_conn, :ok)
285 assert status["id"] == direct.id
290 setup do: oauth_access(["read:lists"])
292 test "list timeline", %{user: user, conn: conn} do
293 other_user = insert(:user)
294 {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."})
295 {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
296 {:ok, list} = Pleroma.List.create("name", user)
297 {:ok, list} = Pleroma.List.follow(list, other_user)
299 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
301 assert [%{"id" => id}] = json_response(conn, :ok)
303 assert id == to_string(activity_two.id)
306 test "list timeline does not leak non-public statuses for unfollowed users", %{
310 other_user = insert(:user)
311 {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
313 {:ok, _activity_two} =
314 CommonAPI.post(other_user, %{
315 "status" => "Marisa is cute.",
316 "visibility" => "private"
319 {:ok, list} = Pleroma.List.create("name", user)
320 {:ok, list} = Pleroma.List.follow(list, other_user)
322 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
324 assert [%{"id" => id}] = json_response(conn, :ok)
326 assert id == to_string(activity_one.id)
330 describe "hashtag" do
331 setup do: oauth_access(["n/a"])
333 @tag capture_log: true
334 test "hashtag timeline", %{conn: conn} do
335 following = insert(:user)
337 {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"})
339 nconn = get(conn, "/api/v1/timelines/tag/2hu")
341 assert [%{"id" => id}] = json_response(nconn, :ok)
343 assert id == to_string(activity.id)
345 # works for different capitalization too
346 nconn = get(conn, "/api/v1/timelines/tag/2HU")
348 assert [%{"id" => id}] = json_response(nconn, :ok)
350 assert id == to_string(activity.id)
353 test "multi-hashtag timeline", %{conn: conn} do
356 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
357 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
358 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
360 any_test = get(conn, "/api/v1/timelines/tag/test", %{"any" => ["test1"]})
362 [status_none, status_test1, status_test] = json_response(any_test, :ok)
364 assert to_string(activity_test.id) == status_test["id"]
365 assert to_string(activity_test1.id) == status_test1["id"]
366 assert to_string(activity_none.id) == status_none["id"]
369 get(conn, "/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
371 assert [status_test1] == json_response(restricted_test, :ok)
373 all_test = get(conn, "/api/v1/timelines/tag/test", %{"all" => ["none"]})
375 assert [status_none] == json_response(all_test, :ok)