Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into remake-remodel-dms
[akkoma] / test / web / mastodon_api / controllers / timeline_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
6 use Pleroma.Web.ConnCase
7
8 import Pleroma.Factory
9 import Tesla.Mock
10
11 alias Pleroma.Config
12 alias Pleroma.User
13 alias Pleroma.Web.CommonAPI
14
15 setup do
16 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
17 :ok
18 end
19
20 describe "home" do
21 setup do: oauth_access(["read:statuses"])
22
23 test "does NOT render account/pleroma/relationship if this is disabled by default", %{
24 user: user,
25 conn: conn
26 } do
27 clear_config([:extensions, :output_relationships_in_statuses_by_default], false)
28
29 other_user = insert(:user)
30
31 {:ok, _} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
32
33 response =
34 conn
35 |> assign(:user, user)
36 |> get("/api/v1/timelines/home")
37 |> json_response_and_validate_schema(200)
38
39 assert Enum.all?(response, fn n ->
40 get_in(n, ["account", "pleroma", "relationship"]) == %{}
41 end)
42 end
43
44 test "the home timeline", %{user: user, conn: conn} do
45 uri = "/api/v1/timelines/home?with_relationships=1"
46
47 following = insert(:user, nickname: "followed")
48 third_user = insert(:user, nickname: "repeated")
49
50 {:ok, _activity} = CommonAPI.post(following, %{status: "post"})
51 {:ok, activity} = CommonAPI.post(third_user, %{status: "repeated post"})
52 {:ok, _, _} = CommonAPI.repeat(activity.id, following)
53
54 # This one should not show up in the TL
55 {:ok, _activity} = CommonAPI.post_chat_message(third_user, user, ":gun:")
56
57 ret_conn = get(conn, uri)
58
59 assert Enum.empty?(json_response_and_validate_schema(ret_conn, :ok))
60
61 {:ok, _user} = User.follow(user, following)
62
63 ret_conn = get(conn, uri)
64
65 assert [
66 %{
67 "reblog" => %{
68 "content" => "repeated post",
69 "account" => %{
70 "pleroma" => %{
71 "relationship" => %{"following" => false, "followed_by" => false}
72 }
73 }
74 },
75 "account" => %{"pleroma" => %{"relationship" => %{"following" => true}}}
76 },
77 %{
78 "content" => "post",
79 "account" => %{
80 "acct" => "followed",
81 "pleroma" => %{"relationship" => %{"following" => true}}
82 }
83 }
84 ] = json_response_and_validate_schema(ret_conn, :ok)
85
86 {:ok, _user} = User.follow(third_user, user)
87
88 ret_conn = get(conn, uri)
89
90 assert [
91 %{
92 "reblog" => %{
93 "content" => "repeated post",
94 "account" => %{
95 "acct" => "repeated",
96 "pleroma" => %{
97 "relationship" => %{"following" => false, "followed_by" => true}
98 }
99 }
100 },
101 "account" => %{"pleroma" => %{"relationship" => %{"following" => true}}}
102 },
103 %{
104 "content" => "post",
105 "account" => %{
106 "acct" => "followed",
107 "pleroma" => %{"relationship" => %{"following" => true}}
108 }
109 }
110 ] = json_response_and_validate_schema(ret_conn, :ok)
111 end
112
113 test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do
114 {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
115 {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
116
117 {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
118
119 {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
120
121 conn = get(conn, "/api/v1/timelines/home?exclude_visibilities[]=direct")
122
123 assert status_ids = json_response_and_validate_schema(conn, :ok) |> Enum.map(& &1["id"])
124 assert public_activity.id in status_ids
125 assert unlisted_activity.id in status_ids
126 assert private_activity.id in status_ids
127 refute direct_activity.id in status_ids
128 end
129 end
130
131 describe "public" do
132 @tag capture_log: true
133 test "the public timeline", %{conn: conn} do
134 following = insert(:user)
135
136 {:ok, _activity} = CommonAPI.post(following, %{status: "test"})
137
138 _activity = insert(:note_activity, local: false)
139
140 conn = get(conn, "/api/v1/timelines/public?local=False")
141
142 assert length(json_response_and_validate_schema(conn, :ok)) == 2
143
144 conn = get(build_conn(), "/api/v1/timelines/public?local=True")
145
146 assert [%{"content" => "test"}] = json_response_and_validate_schema(conn, :ok)
147
148 conn = get(build_conn(), "/api/v1/timelines/public?local=1")
149
150 assert [%{"content" => "test"}] = json_response_and_validate_schema(conn, :ok)
151 end
152
153 test "the public timeline includes only public statuses for an authenticated user" do
154 %{user: user, conn: conn} = oauth_access(["read:statuses"])
155
156 {:ok, _activity} = CommonAPI.post(user, %{status: "test"})
157 {:ok, _activity} = CommonAPI.post(user, %{status: "test", visibility: "private"})
158 {:ok, _activity} = CommonAPI.post(user, %{status: "test", visibility: "unlisted"})
159 {:ok, _activity} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
160
161 res_conn = get(conn, "/api/v1/timelines/public")
162 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
163 end
164 end
165
166 defp local_and_remote_activities do
167 insert(:note_activity)
168 insert(:note_activity, local: false)
169 :ok
170 end
171
172 describe "public with restrict unauthenticated timeline for local and federated timelines" do
173 setup do: local_and_remote_activities()
174
175 setup do: clear_config([:restrict_unauthenticated, :timelines, :local], true)
176
177 setup do: clear_config([:restrict_unauthenticated, :timelines, :federated], true)
178
179 test "if user is unauthenticated", %{conn: conn} do
180 res_conn = get(conn, "/api/v1/timelines/public?local=true")
181
182 assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
183 "error" => "authorization required for timeline view"
184 }
185
186 res_conn = get(conn, "/api/v1/timelines/public?local=false")
187
188 assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
189 "error" => "authorization required for timeline view"
190 }
191 end
192
193 test "if user is authenticated" do
194 %{conn: conn} = oauth_access(["read:statuses"])
195
196 res_conn = get(conn, "/api/v1/timelines/public?local=true")
197 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
198
199 res_conn = get(conn, "/api/v1/timelines/public?local=false")
200 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
201 end
202 end
203
204 describe "public with restrict unauthenticated timeline for local" do
205 setup do: local_and_remote_activities()
206
207 setup do: clear_config([:restrict_unauthenticated, :timelines, :local], true)
208
209 test "if user is unauthenticated", %{conn: conn} do
210 res_conn = get(conn, "/api/v1/timelines/public?local=true")
211
212 assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
213 "error" => "authorization required for timeline view"
214 }
215
216 res_conn = get(conn, "/api/v1/timelines/public?local=false")
217 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
218 end
219
220 test "if user is authenticated", %{conn: _conn} do
221 %{conn: conn} = oauth_access(["read:statuses"])
222
223 res_conn = get(conn, "/api/v1/timelines/public?local=true")
224 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
225
226 res_conn = get(conn, "/api/v1/timelines/public?local=false")
227 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
228 end
229 end
230
231 describe "public with restrict unauthenticated timeline for remote" do
232 setup do: local_and_remote_activities()
233
234 setup do: clear_config([:restrict_unauthenticated, :timelines, :federated], true)
235
236 test "if user is unauthenticated", %{conn: conn} do
237 res_conn = get(conn, "/api/v1/timelines/public?local=true")
238 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
239
240 res_conn = get(conn, "/api/v1/timelines/public?local=false")
241
242 assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
243 "error" => "authorization required for timeline view"
244 }
245 end
246
247 test "if user is authenticated", %{conn: _conn} do
248 %{conn: conn} = oauth_access(["read:statuses"])
249
250 res_conn = get(conn, "/api/v1/timelines/public?local=true")
251 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
252
253 res_conn = get(conn, "/api/v1/timelines/public?local=false")
254 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
255 end
256 end
257
258 describe "direct" do
259 test "direct timeline", %{conn: conn} do
260 user_one = insert(:user)
261 user_two = insert(:user)
262
263 {:ok, user_two} = User.follow(user_two, user_one)
264
265 {:ok, direct} =
266 CommonAPI.post(user_one, %{
267 status: "Hi @#{user_two.nickname}!",
268 visibility: "direct"
269 })
270
271 {:ok, _follower_only} =
272 CommonAPI.post(user_one, %{
273 status: "Hi @#{user_two.nickname}!",
274 visibility: "private"
275 })
276
277 conn_user_two =
278 conn
279 |> assign(:user, user_two)
280 |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
281
282 # Only direct should be visible here
283 res_conn = get(conn_user_two, "api/v1/timelines/direct")
284
285 assert [status] = json_response_and_validate_schema(res_conn, :ok)
286
287 assert %{"visibility" => "direct"} = status
288 assert status["url"] != direct.data["id"]
289
290 # User should be able to see their own direct message
291 res_conn =
292 build_conn()
293 |> assign(:user, user_one)
294 |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"]))
295 |> get("api/v1/timelines/direct")
296
297 [status] = json_response_and_validate_schema(res_conn, :ok)
298
299 assert %{"visibility" => "direct"} = status
300
301 # Both should be visible here
302 res_conn = get(conn_user_two, "api/v1/timelines/home")
303
304 [_s1, _s2] = json_response_and_validate_schema(res_conn, :ok)
305
306 # Test pagination
307 Enum.each(1..20, fn _ ->
308 {:ok, _} =
309 CommonAPI.post(user_one, %{
310 status: "Hi @#{user_two.nickname}!",
311 visibility: "direct"
312 })
313 end)
314
315 res_conn = get(conn_user_two, "api/v1/timelines/direct")
316
317 statuses = json_response_and_validate_schema(res_conn, :ok)
318 assert length(statuses) == 20
319
320 max_id = List.last(statuses)["id"]
321
322 res_conn = get(conn_user_two, "api/v1/timelines/direct?max_id=#{max_id}")
323
324 assert [status] = json_response_and_validate_schema(res_conn, :ok)
325
326 assert status["url"] != direct.data["id"]
327 end
328
329 test "doesn't include DMs from blocked users" do
330 %{user: blocker, conn: conn} = oauth_access(["read:statuses"])
331 blocked = insert(:user)
332 other_user = insert(:user)
333 {:ok, _user_relationship} = User.block(blocker, blocked)
334
335 {:ok, _blocked_direct} =
336 CommonAPI.post(blocked, %{
337 status: "Hi @#{blocker.nickname}!",
338 visibility: "direct"
339 })
340
341 {:ok, direct} =
342 CommonAPI.post(other_user, %{
343 status: "Hi @#{blocker.nickname}!",
344 visibility: "direct"
345 })
346
347 res_conn = get(conn, "api/v1/timelines/direct")
348
349 [status] = json_response_and_validate_schema(res_conn, :ok)
350 assert status["id"] == direct.id
351 end
352 end
353
354 describe "list" do
355 setup do: oauth_access(["read:lists"])
356
357 test "list timeline", %{user: user, conn: conn} do
358 other_user = insert(:user)
359 {:ok, _activity_one} = CommonAPI.post(user, %{status: "Marisa is cute."})
360 {:ok, activity_two} = CommonAPI.post(other_user, %{status: "Marisa is cute."})
361 {:ok, list} = Pleroma.List.create("name", user)
362 {:ok, list} = Pleroma.List.follow(list, other_user)
363
364 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
365
366 assert [%{"id" => id}] = json_response_and_validate_schema(conn, :ok)
367
368 assert id == to_string(activity_two.id)
369 end
370
371 test "list timeline does not leak non-public statuses for unfollowed users", %{
372 user: user,
373 conn: conn
374 } do
375 other_user = insert(:user)
376 {:ok, activity_one} = CommonAPI.post(other_user, %{status: "Marisa is cute."})
377
378 {:ok, _activity_two} =
379 CommonAPI.post(other_user, %{
380 status: "Marisa is cute.",
381 visibility: "private"
382 })
383
384 {:ok, list} = Pleroma.List.create("name", user)
385 {:ok, list} = Pleroma.List.follow(list, other_user)
386
387 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
388
389 assert [%{"id" => id}] = json_response_and_validate_schema(conn, :ok)
390
391 assert id == to_string(activity_one.id)
392 end
393 end
394
395 describe "hashtag" do
396 setup do: oauth_access(["n/a"])
397
398 @tag capture_log: true
399 test "hashtag timeline", %{conn: conn} do
400 following = insert(:user)
401
402 {:ok, activity} = CommonAPI.post(following, %{status: "test #2hu"})
403
404 nconn = get(conn, "/api/v1/timelines/tag/2hu")
405
406 assert [%{"id" => id}] = json_response_and_validate_schema(nconn, :ok)
407
408 assert id == to_string(activity.id)
409
410 # works for different capitalization too
411 nconn = get(conn, "/api/v1/timelines/tag/2HU")
412
413 assert [%{"id" => id}] = json_response_and_validate_schema(nconn, :ok)
414
415 assert id == to_string(activity.id)
416 end
417
418 test "multi-hashtag timeline", %{conn: conn} do
419 user = insert(:user)
420
421 {:ok, activity_test} = CommonAPI.post(user, %{status: "#test"})
422 {:ok, activity_test1} = CommonAPI.post(user, %{status: "#test #test1"})
423 {:ok, activity_none} = CommonAPI.post(user, %{status: "#test #none"})
424
425 any_test = get(conn, "/api/v1/timelines/tag/test?any[]=test1")
426
427 [status_none, status_test1, status_test] = json_response_and_validate_schema(any_test, :ok)
428
429 assert to_string(activity_test.id) == status_test["id"]
430 assert to_string(activity_test1.id) == status_test1["id"]
431 assert to_string(activity_none.id) == status_none["id"]
432
433 restricted_test = get(conn, "/api/v1/timelines/tag/test?all[]=test1&none[]=none")
434
435 assert [status_test1] == json_response_and_validate_schema(restricted_test, :ok)
436
437 all_test = get(conn, "/api/v1/timelines/tag/test?all[]=none")
438
439 assert [status_none] == json_response_and_validate_schema(all_test, :ok)
440 end
441 end
442 end