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