1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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
12 alias Pleroma.Web.CommonAPI
15 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
20 setup do: oauth_access(["read:statuses"])
22 test "does NOT embed account/pleroma/relationship in statuses", %{
26 other_user = insert(:user)
28 {:ok, _} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
32 |> assign(:user, user)
33 |> get("/api/v1/timelines/home")
34 |> json_response_and_validate_schema(200)
36 assert Enum.all?(response, fn n ->
37 get_in(n, ["account", "pleroma", "relationship"]) == %{}
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"})
45 {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
47 {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
49 conn = get(conn, "/api/v1/timelines/home?exclude_visibilities[]=direct")
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
58 test "muted emotions", %{user: user, conn: conn} do
59 other_user = insert(:user)
60 {:ok, activity} = CommonAPI.post(user, %{status: "."})
62 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
63 User.mute(user, other_user)
67 |> assign(:user, user)
68 |> get("/api/v1/timelines/home")
69 |> json_response_and_validate_schema(200)
74 "emoji_reactions" => []
81 |> assign(:user, user)
82 |> get("/api/v1/timelines/home?with_muted=true")
83 |> json_response_and_validate_schema(200)
88 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
94 test "local/remote filtering", %{conn: conn, user: user} do
96 remote = insert(:user, local: false)
98 {:ok, user, local} = User.follow(user, local)
99 {:ok, _user, remote} = User.follow(user, remote)
104 "to" => ["https://www.w3.org/ns/activitystreams#Public", User.ap_followers(local)]
110 insert(:note_activity, %{
112 recipients: ["https://www.w3.org/ns/activitystreams#Public", User.ap_followers(local)],
119 "to" => ["https://www.w3.org/ns/activitystreams#Public", User.ap_followers(remote)]
125 insert(:note_activity, %{
127 recipients: ["https://www.w3.org/ns/activitystreams#Public", User.ap_followers(remote)],
134 |> get("/api/v1/timelines/home")
135 |> json_response_and_validate_schema(200)
137 without_filter_ids = Enum.map(resp1, & &1["id"])
139 assert activity1.id in without_filter_ids
140 assert activity2.id in without_filter_ids
144 |> get("/api/v1/timelines/home?local=true")
145 |> json_response_and_validate_schema(200)
147 only_local_ids = Enum.map(resp2, & &1["id"])
149 assert activity1.id in only_local_ids
150 refute activity2.id in only_local_ids
154 |> get("/api/v1/timelines/home?only_remote=true")
155 |> json_response_and_validate_schema(200)
157 only_remote_ids = Enum.map(resp3, & &1["id"])
159 refute activity1.id in only_remote_ids
160 assert activity2.id in only_remote_ids
164 |> get("/api/v1/timelines/home?only_remote=true&local=true")
165 |> json_response_and_validate_schema(200)
170 test "only_media flag", %{conn: conn, user: user} do
171 other = insert(:user)
172 {:ok, _, other} = User.follow(user, other)
175 insert(:note_activity,
177 recipients: ["https://www.w3.org/ns/activitystreams#Public", User.ap_followers(other)]
185 "mediaType" => "image/jpeg",
186 "name" => "an_image.jpg",
187 "type" => "Document",
191 "http://localhost:4001/media/8270697e-104f-4a54-a7c1-514bb6713f2c/some_image.jpg",
192 "mediaType" => "image/jpeg",
203 insert(:note_activity, %{
205 recipients: ["https://www.w3.org/ns/activitystreams#Public", User.ap_followers(other)],
211 |> get("/api/v1/timelines/home")
212 |> json_response_and_validate_schema(200)
214 without_filter_ids = Enum.map(resp1, & &1["id"])
216 assert without_media.id in without_filter_ids
217 assert with_media.id in without_filter_ids
221 |> get("/api/v1/timelines/home?only_media=true")
222 |> json_response_and_validate_schema(200)
224 only_media_ids = Enum.map(resp2, & &1["id"])
226 refute without_media.id in only_media_ids
227 assert with_media.id in only_media_ids
232 @tag capture_log: true
233 test "the public timeline", %{conn: conn} do
236 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
238 _activity = insert(:note_activity, local: false)
240 conn = get(conn, "/api/v1/timelines/public?local=False")
242 assert length(json_response_and_validate_schema(conn, :ok)) == 2
244 conn = get(build_conn(), "/api/v1/timelines/public?local=True")
246 assert [%{"content" => "test"}] = json_response_and_validate_schema(conn, :ok)
248 conn = get(build_conn(), "/api/v1/timelines/public?local=1")
250 assert [%{"content" => "test"}] = json_response_and_validate_schema(conn, :ok)
252 # does not contain repeats
253 {:ok, _} = CommonAPI.repeat(activity.id, user)
255 conn = get(build_conn(), "/api/v1/timelines/public?local=true")
257 assert [_] = json_response_and_validate_schema(conn, :ok)
260 test "the public timeline includes only public statuses for an authenticated user" do
261 %{user: user, conn: conn} = oauth_access(["read:statuses"])
263 {:ok, _activity} = CommonAPI.post(user, %{status: "test"})
264 {:ok, _activity} = CommonAPI.post(user, %{status: "test", visibility: "private"})
265 {:ok, _activity} = CommonAPI.post(user, %{status: "test", visibility: "unlisted"})
266 {:ok, _activity} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
268 res_conn = get(conn, "/api/v1/timelines/public")
269 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
272 test "doesn't return replies if follower is posting with blocked user" do
273 %{conn: conn, user: blocker} = oauth_access(["read:statuses"])
274 [blockee, friend] = insert_list(2, :user)
275 {:ok, blocker, friend} = User.follow(blocker, friend)
276 {:ok, _} = User.block(blocker, blockee)
278 conn = assign(conn, :user, blocker)
280 {:ok, %{id: activity_id} = activity} = CommonAPI.post(friend, %{status: "hey!"})
282 {:ok, reply_from_blockee} =
283 CommonAPI.post(blockee, %{status: "heya", in_reply_to_status_id: activity})
285 {:ok, _reply_from_friend} =
286 CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee})
288 # Still shows replies from yourself
289 {:ok, %{id: reply_from_me}} =
290 CommonAPI.post(blocker, %{status: "status", in_reply_to_status_id: reply_from_blockee})
293 get(conn, "/api/v1/timelines/public")
294 |> json_response_and_validate_schema(200)
296 assert length(response) == 2
297 [%{"id" => ^reply_from_me}, %{"id" => ^activity_id}] = response
300 test "doesn't return replies if follow is posting with users from blocked domain" do
301 %{conn: conn, user: blocker} = oauth_access(["read:statuses"])
302 friend = insert(:user)
303 blockee = insert(:user, ap_id: "https://example.com/users/blocked")
304 {:ok, blocker, friend} = User.follow(blocker, friend)
305 {:ok, blocker} = User.block_domain(blocker, "example.com")
307 conn = assign(conn, :user, blocker)
309 {:ok, %{id: activity_id} = activity} = CommonAPI.post(friend, %{status: "hey!"})
311 {:ok, reply_from_blockee} =
312 CommonAPI.post(blockee, %{status: "heya", in_reply_to_status_id: activity})
314 {:ok, _reply_from_friend} =
315 CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee})
317 res_conn = get(conn, "/api/v1/timelines/public")
319 activities = json_response_and_validate_schema(res_conn, 200)
320 [%{"id" => ^activity_id}] = activities
323 test "can be filtered by instance", %{conn: conn} do
324 user = insert(:user, ap_id: "https://lain.com/users/lain")
325 insert(:note_activity, local: false)
326 insert(:note_activity, local: false)
328 {:ok, _} = CommonAPI.post(user, %{status: "test"})
330 conn = get(conn, "/api/v1/timelines/public?instance=lain.com")
332 assert length(json_response_and_validate_schema(conn, :ok)) == 1
335 test "muted emotions", %{conn: conn} do
337 token = insert(:oauth_token, user: user, scopes: ["read:statuses"])
341 |> assign(:user, user)
342 |> assign(:token, token)
344 other_user = insert(:user)
345 {:ok, activity} = CommonAPI.post(user, %{status: "."})
347 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
348 User.mute(user, other_user)
352 |> get("/api/v1/timelines/public")
353 |> json_response_and_validate_schema(200)
358 "emoji_reactions" => []
365 |> get("/api/v1/timelines/public?with_muted=true")
366 |> json_response_and_validate_schema(200)
371 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
378 defp local_and_remote_activities do
379 insert(:note_activity)
380 insert(:note_activity, local: false)
384 describe "public with restrict unauthenticated timeline for local and federated timelines" do
385 setup do: local_and_remote_activities()
387 setup do: clear_config([:restrict_unauthenticated, :timelines, :local], true)
389 setup do: clear_config([:restrict_unauthenticated, :timelines, :federated], true)
391 test "if user is unauthenticated", %{conn: conn} do
392 res_conn = get(conn, "/api/v1/timelines/public?local=true")
394 assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
395 "error" => "authorization required for timeline view"
398 res_conn = get(conn, "/api/v1/timelines/public?local=false")
400 assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
401 "error" => "authorization required for timeline view"
405 test "if user is authenticated" do
406 %{conn: conn} = oauth_access(["read:statuses"])
408 res_conn = get(conn, "/api/v1/timelines/public?local=true")
409 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
411 res_conn = get(conn, "/api/v1/timelines/public?local=false")
412 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
416 describe "public with restrict unauthenticated timeline for local" do
417 setup do: local_and_remote_activities()
419 setup do: clear_config([:restrict_unauthenticated, :timelines, :local], true)
421 test "if user is unauthenticated", %{conn: conn} do
422 res_conn = get(conn, "/api/v1/timelines/public?local=true")
424 assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
425 "error" => "authorization required for timeline view"
428 res_conn = get(conn, "/api/v1/timelines/public?local=false")
429 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
432 test "if user is authenticated", %{conn: _conn} do
433 %{conn: conn} = oauth_access(["read:statuses"])
435 res_conn = get(conn, "/api/v1/timelines/public?local=true")
436 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
438 res_conn = get(conn, "/api/v1/timelines/public?local=false")
439 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
443 describe "public with restrict unauthenticated timeline for remote" do
444 setup do: local_and_remote_activities()
446 setup do: clear_config([:restrict_unauthenticated, :timelines, :federated], true)
448 test "if user is unauthenticated", %{conn: conn} do
449 res_conn = get(conn, "/api/v1/timelines/public?local=true")
450 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
452 res_conn = get(conn, "/api/v1/timelines/public?local=false")
454 assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
455 "error" => "authorization required for timeline view"
459 test "if user is authenticated", %{conn: _conn} do
460 %{conn: conn} = oauth_access(["read:statuses"])
462 res_conn = get(conn, "/api/v1/timelines/public?local=true")
463 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
465 res_conn = get(conn, "/api/v1/timelines/public?local=false")
466 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
471 test "direct timeline", %{conn: conn} do
472 user_one = insert(:user)
473 user_two = insert(:user)
475 {:ok, user_two, user_one} = User.follow(user_two, user_one)
478 CommonAPI.post(user_one, %{
479 status: "Hi @#{user_two.nickname}!",
483 {:ok, _follower_only} =
484 CommonAPI.post(user_one, %{
485 status: "Hi @#{user_two.nickname}!",
486 visibility: "private"
491 |> assign(:user, user_two)
492 |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
494 # Only direct should be visible here
495 res_conn = get(conn_user_two, "api/v1/timelines/direct")
497 assert [status] = json_response_and_validate_schema(res_conn, :ok)
499 assert %{"visibility" => "direct"} = status
500 assert status["url"] != direct.data["id"]
502 # User should be able to see their own direct message
505 |> assign(:user, user_one)
506 |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"]))
507 |> get("api/v1/timelines/direct")
509 [status] = json_response_and_validate_schema(res_conn, :ok)
511 assert %{"visibility" => "direct"} = status
513 # Both should be visible here
514 res_conn = get(conn_user_two, "api/v1/timelines/home")
516 [_s1, _s2] = json_response_and_validate_schema(res_conn, :ok)
519 Enum.each(1..20, fn _ ->
521 CommonAPI.post(user_one, %{
522 status: "Hi @#{user_two.nickname}!",
527 res_conn = get(conn_user_two, "api/v1/timelines/direct")
529 statuses = json_response_and_validate_schema(res_conn, :ok)
530 assert length(statuses) == 20
532 max_id = List.last(statuses)["id"]
534 res_conn = get(conn_user_two, "api/v1/timelines/direct?max_id=#{max_id}")
536 assert [status] = json_response_and_validate_schema(res_conn, :ok)
538 assert status["url"] != direct.data["id"]
541 test "doesn't include DMs from blocked users" do
542 %{user: blocker, conn: conn} = oauth_access(["read:statuses"])
543 blocked = insert(:user)
544 other_user = insert(:user)
545 {:ok, _user_relationship} = User.block(blocker, blocked)
547 {:ok, _blocked_direct} =
548 CommonAPI.post(blocked, %{
549 status: "Hi @#{blocker.nickname}!",
554 CommonAPI.post(other_user, %{
555 status: "Hi @#{blocker.nickname}!",
559 res_conn = get(conn, "api/v1/timelines/direct")
561 [status] = json_response_and_validate_schema(res_conn, :ok)
562 assert status["id"] == direct.id
567 setup do: oauth_access(["read:lists"])
569 test "does not contain retoots", %{user: user, conn: conn} do
570 other_user = insert(:user)
571 {:ok, activity_one} = CommonAPI.post(user, %{status: "Marisa is cute."})
572 {:ok, activity_two} = CommonAPI.post(other_user, %{status: "Marisa is stupid."})
573 {:ok, _} = CommonAPI.repeat(activity_one.id, other_user)
575 {:ok, list} = Pleroma.List.create("name", user)
576 {:ok, list} = Pleroma.List.follow(list, other_user)
578 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
580 assert [%{"id" => id}] = json_response_and_validate_schema(conn, :ok)
582 assert id == to_string(activity_two.id)
585 test "works with pagination", %{user: user, conn: conn} do
586 other_user = insert(:user)
587 {:ok, list} = Pleroma.List.create("name", user)
588 {:ok, list} = Pleroma.List.follow(list, other_user)
590 Enum.each(1..30, fn i ->
591 CommonAPI.post(other_user, %{status: "post number #{i}"})
595 get(conn, "/api/v1/timelines/list/#{list.id}?limit=1")
596 |> json_response_and_validate_schema(:ok)
598 assert length(res) == 1
603 get(conn, "/api/v1/timelines/list/#{list.id}?max_id=#{first["id"]}&limit=30")
604 |> json_response_and_validate_schema(:ok)
606 assert length(res) == 29
609 test "list timeline", %{user: user, conn: conn} do
610 other_user = insert(:user)
611 {:ok, _activity_one} = CommonAPI.post(user, %{status: "Marisa is cute."})
612 {:ok, activity_two} = CommonAPI.post(other_user, %{status: "Marisa is cute."})
613 {:ok, list} = Pleroma.List.create("name", user)
614 {:ok, list} = Pleroma.List.follow(list, other_user)
616 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
618 assert [%{"id" => id}] = json_response_and_validate_schema(conn, :ok)
620 assert id == to_string(activity_two.id)
623 test "list timeline does not leak non-public statuses for unfollowed users", %{
627 other_user = insert(:user)
628 {:ok, activity_one} = CommonAPI.post(other_user, %{status: "Marisa is cute."})
630 {:ok, _activity_two} =
631 CommonAPI.post(other_user, %{
632 status: "Marisa is cute.",
633 visibility: "private"
636 {:ok, list} = Pleroma.List.create("name", user)
637 {:ok, list} = Pleroma.List.follow(list, other_user)
639 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
641 assert [%{"id" => id}] = json_response_and_validate_schema(conn, :ok)
643 assert id == to_string(activity_one.id)
646 test "muted emotions", %{user: user, conn: conn} do
647 user2 = insert(:user)
648 user3 = insert(:user)
649 {:ok, activity} = CommonAPI.post(user2, %{status: "."})
651 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user3, "🎅")
652 User.mute(user, user3)
654 {:ok, list} = Pleroma.List.create("name", user)
655 {:ok, list} = Pleroma.List.follow(list, user2)
659 |> get("/api/v1/timelines/list/#{list.id}")
660 |> json_response_and_validate_schema(200)
665 "emoji_reactions" => []
672 |> get("/api/v1/timelines/list/#{list.id}?with_muted=true")
673 |> json_response_and_validate_schema(200)
678 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
685 describe "hashtag" do
686 setup do: oauth_access(["n/a"])
688 @tag capture_log: true
689 test "hashtag timeline", %{conn: conn} do
690 following = insert(:user)
692 {:ok, activity} = CommonAPI.post(following, %{status: "test #2hu"})
694 nconn = get(conn, "/api/v1/timelines/tag/2hu")
696 assert [%{"id" => id}] = json_response_and_validate_schema(nconn, :ok)
698 assert id == to_string(activity.id)
700 # works for different capitalization too
701 nconn = get(conn, "/api/v1/timelines/tag/2HU")
703 assert [%{"id" => id}] = json_response_and_validate_schema(nconn, :ok)
705 assert id == to_string(activity.id)
708 test "multi-hashtag timeline", %{conn: conn} do
711 {:ok, activity_test} = CommonAPI.post(user, %{status: "#test"})
712 {:ok, activity_test1} = CommonAPI.post(user, %{status: "#test #test1"})
713 {:ok, activity_none} = CommonAPI.post(user, %{status: "#test #none"})
715 any_test = get(conn, "/api/v1/timelines/tag/test?any[]=test1")
717 [status_none, status_test1, status_test] = json_response_and_validate_schema(any_test, :ok)
719 assert to_string(activity_test.id) == status_test["id"]
720 assert to_string(activity_test1.id) == status_test1["id"]
721 assert to_string(activity_none.id) == status_none["id"]
723 restricted_test = get(conn, "/api/v1/timelines/tag/test?all[]=test1&none[]=none")
725 assert [status_test1] == json_response_and_validate_schema(restricted_test, :ok)
727 all_test = get(conn, "/api/v1/timelines/tag/test?all[]=none")
729 assert [status_none] == json_response_and_validate_schema(all_test, :ok)
732 test "muted emotions", %{conn: conn} do
734 token = insert(:oauth_token, user: user, scopes: ["read:statuses"])
738 |> assign(:user, user)
739 |> assign(:token, token)
741 other_user = insert(:user)
742 {:ok, activity} = CommonAPI.post(user, %{status: "test #2hu"})
744 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
745 User.mute(user, other_user)
749 |> get("/api/v1/timelines/tag/2hu")
750 |> json_response_and_validate_schema(200)
755 "emoji_reactions" => []
762 |> get("/api/v1/timelines/tag/2hu?with_muted=true")
763 |> json_response_and_validate_schema(200)
768 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
775 describe "hashtag timeline handling of :restrict_unauthenticated setting" do
778 {:ok, activity1} = CommonAPI.post(user, %{status: "test #tag1"})
779 {:ok, _activity2} = CommonAPI.post(user, %{status: "test #tag1"})
782 |> Ecto.Changeset.change(%{local: false})
783 |> Pleroma.Repo.update()
785 base_uri = "/api/v1/timelines/tag/tag1"
786 error_response = %{"error" => "authorization required for timeline view"}
788 %{base_uri: base_uri, error_response: error_response}
791 defp ensure_authenticated_access(base_uri) do
792 %{conn: auth_conn} = oauth_access(["read:statuses"])
794 res_conn = get(auth_conn, "#{base_uri}?local=true")
795 assert length(json_response(res_conn, 200)) == 1
797 res_conn = get(auth_conn, "#{base_uri}?local=false")
798 assert length(json_response(res_conn, 200)) == 2
801 test "with default settings on private instances, returns 403 for unauthenticated users", %{
804 error_response: error_response
806 clear_config([:instance, :public], false)
807 clear_config([:restrict_unauthenticated, :timelines])
809 for local <- [true, false] do
810 res_conn = get(conn, "#{base_uri}?local=#{local}")
812 assert json_response(res_conn, :unauthorized) == error_response
815 ensure_authenticated_access(base_uri)
818 test "with `%{local: true, federated: true}`, returns 403 for unauthenticated users", %{
821 error_response: error_response
823 clear_config([:restrict_unauthenticated, :timelines, :local], true)
824 clear_config([:restrict_unauthenticated, :timelines, :federated], true)
826 for local <- [true, false] do
827 res_conn = get(conn, "#{base_uri}?local=#{local}")
829 assert json_response(res_conn, :unauthorized) == error_response
832 ensure_authenticated_access(base_uri)
835 test "with `%{local: false, federated: true}`, forbids unauthenticated access to federated timeline",
836 %{conn: conn, base_uri: base_uri, error_response: error_response} do
837 clear_config([:restrict_unauthenticated, :timelines, :local], false)
838 clear_config([:restrict_unauthenticated, :timelines, :federated], true)
840 res_conn = get(conn, "#{base_uri}?local=true")
841 assert length(json_response(res_conn, 200)) == 1
843 res_conn = get(conn, "#{base_uri}?local=false")
844 assert json_response(res_conn, :unauthorized) == error_response
846 ensure_authenticated_access(base_uri)
849 test "with `%{local: true, federated: false}`, forbids unauthenticated access to public timeline" <>
850 "(but not to local public activities which are delivered as part of federated timeline)",
851 %{conn: conn, base_uri: base_uri, error_response: error_response} do
852 clear_config([:restrict_unauthenticated, :timelines, :local], true)
853 clear_config([:restrict_unauthenticated, :timelines, :federated], false)
855 res_conn = get(conn, "#{base_uri}?local=true")
856 assert json_response(res_conn, :unauthorized) == error_response
858 # Note: local activities get delivered as part of federated timeline
859 res_conn = get(conn, "#{base_uri}?local=false")
860 assert length(json_response(res_conn, 200)) == 2
862 ensure_authenticated_access(base_uri)