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.ActivityPub.ActivityPubTest do
7 use Oban.Testing, repo: Pleroma.Repo
10 alias Pleroma.Builders.ActivityBuilder
12 alias Pleroma.Notification
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.ActivityPub.Utils
17 alias Pleroma.Web.AdminAPI.AccountView
18 alias Pleroma.Web.CommonAPI
19 alias Pleroma.Web.Federator
21 import ExUnit.CaptureLog
23 import Pleroma.Factory
27 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
31 setup do: clear_config([:instance, :federating])
33 describe "streaming out participations" do
34 test "it streams them out" do
36 {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
38 {:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity)
41 conversation.participations
42 |> Repo.preload(:user)
44 with_mock Pleroma.Web.Streamer,
45 stream: fn _, _ -> nil end do
46 ActivityPub.stream_out_participations(conversation.participations)
48 assert called(Pleroma.Web.Streamer.stream("participation", participations))
52 test "streams them out on activity creation" do
53 user_one = insert(:user)
54 user_two = insert(:user)
56 with_mock Pleroma.Web.Streamer,
57 stream: fn _, _ -> nil end do
59 CommonAPI.post(user_one, %{
60 "status" => "@#{user_two.nickname}",
61 "visibility" => "direct"
65 activity.data["context"]
66 |> Pleroma.Conversation.get_for_ap_id()
67 |> Repo.preload(participations: :user)
69 assert called(Pleroma.Web.Streamer.stream("participation", conversation.participations))
74 describe "fetching restricted by visibility" do
75 test "it restricts by the appropriate visibility" do
78 {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
80 {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
82 {:ok, unlisted_activity} =
83 CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
85 {:ok, private_activity} =
86 CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
89 ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id})
91 assert activities == [direct_activity]
94 ActivityPub.fetch_activities([], %{:visibility => "unlisted", "actor_id" => user.ap_id})
96 assert activities == [unlisted_activity]
99 ActivityPub.fetch_activities([], %{:visibility => "private", "actor_id" => user.ap_id})
101 assert activities == [private_activity]
104 ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id})
106 assert activities == [public_activity]
109 ActivityPub.fetch_activities([], %{
110 :visibility => ~w[private public],
111 "actor_id" => user.ap_id
114 assert activities == [public_activity, private_activity]
118 describe "fetching excluded by visibility" do
119 test "it excludes by the appropriate visibility" do
122 {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
124 {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
126 {:ok, unlisted_activity} =
127 CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
129 {:ok, private_activity} =
130 CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
133 ActivityPub.fetch_activities([], %{
134 "exclude_visibilities" => "direct",
135 "actor_id" => user.ap_id
138 assert public_activity in activities
139 assert unlisted_activity in activities
140 assert private_activity in activities
141 refute direct_activity in activities
144 ActivityPub.fetch_activities([], %{
145 "exclude_visibilities" => "unlisted",
146 "actor_id" => user.ap_id
149 assert public_activity in activities
150 refute unlisted_activity in activities
151 assert private_activity in activities
152 assert direct_activity in activities
155 ActivityPub.fetch_activities([], %{
156 "exclude_visibilities" => "private",
157 "actor_id" => user.ap_id
160 assert public_activity in activities
161 assert unlisted_activity in activities
162 refute private_activity in activities
163 assert direct_activity in activities
166 ActivityPub.fetch_activities([], %{
167 "exclude_visibilities" => "public",
168 "actor_id" => user.ap_id
171 refute public_activity in activities
172 assert unlisted_activity in activities
173 assert private_activity in activities
174 assert direct_activity in activities
178 describe "building a user from his ap id" do
179 test "it returns a user" do
180 user_id = "http://mastodon.example.org/users/admin"
181 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
182 assert user.ap_id == user_id
183 assert user.nickname == "admin@mastodon.example.org"
184 assert user.ap_enabled
185 assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
188 test "it returns a user that is invisible" do
189 user_id = "http://mastodon.example.org/users/relay"
190 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
191 assert User.invisible?(user)
194 test "it fetches the appropriate tag-restricted posts" do
197 {:ok, status_one} = CommonAPI.post(user, %{"status" => ". #test"})
198 {:ok, status_two} = CommonAPI.post(user, %{"status" => ". #essais"})
199 {:ok, status_three} = CommonAPI.post(user, %{"status" => ". #test #reject"})
201 fetch_one = ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => "test"})
204 ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => ["test", "essais"]})
207 ActivityPub.fetch_activities([], %{
209 "tag" => ["test", "essais"],
210 "tag_reject" => ["reject"]
214 ActivityPub.fetch_activities([], %{
217 "tag_all" => ["test", "reject"]
220 assert fetch_one == [status_one, status_three]
221 assert fetch_two == [status_one, status_two, status_three]
222 assert fetch_three == [status_one, status_two]
223 assert fetch_four == [status_three]
227 describe "insertion" do
228 test "drops activities beyond a certain limit" do
229 limit = Config.get([:instance, :remote_limit])
232 :crypto.strong_rand_bytes(limit + 1)
234 |> binary_part(0, limit + 1)
239 "content" => random_text
243 assert {:error, {:remote_limit_error, _}} = ActivityPub.insert(data)
246 test "doesn't drop activities with content being null" do
250 "actor" => user.ap_id,
253 "actor" => user.ap_id,
260 assert {:ok, _} = ActivityPub.insert(data)
263 test "returns the activity if one with the same id is already in" do
264 activity = insert(:note_activity)
265 {:ok, new_activity} = ActivityPub.insert(activity.data)
267 assert activity.id == new_activity.id
270 test "inserts a given map into the activity database, giving it an id if it has none." do
274 "actor" => user.ap_id,
277 "actor" => user.ap_id,
284 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
285 assert activity.data["ok"] == data["ok"]
286 assert is_binary(activity.data["id"])
292 "actor" => user.ap_id,
294 "context" => "blabla",
296 "actor" => user.ap_id,
303 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
304 assert activity.data["ok"] == data["ok"]
305 assert activity.data["id"] == given_id
306 assert activity.data["context"] == "blabla"
307 assert activity.data["context_id"]
310 test "adds a context when none is there" do
314 "actor" => user.ap_id,
317 "actor" => user.ap_id,
324 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
325 object = Pleroma.Object.normalize(activity)
327 assert is_binary(activity.data["context"])
328 assert is_binary(object.data["context"])
329 assert activity.data["context_id"]
330 assert object.data["context_id"]
333 test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
337 "actor" => user.ap_id,
340 "actor" => user.ap_id,
347 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
348 assert object = Object.normalize(activity)
349 assert is_binary(object.data["id"])
353 describe "listen activities" do
354 test "does not increase user note count" do
358 ActivityPub.listen(%{
359 to: ["https://www.w3.org/ns/activitystreams#Public"],
363 "actor" => user.ap_id,
364 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
366 "title" => "lain radio episode 1",
372 assert activity.actor == user.ap_id
374 user = User.get_cached_by_id(user.id)
375 assert user.note_count == 0
378 test "can be fetched into a timeline" do
379 _listen_activity_1 = insert(:listen)
380 _listen_activity_2 = insert(:listen)
381 _listen_activity_3 = insert(:listen)
383 timeline = ActivityPub.fetch_activities([], %{"type" => ["Listen"]})
385 assert length(timeline) == 3
389 describe "create activities" do
390 test "it reverts create" do
393 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
394 assert {:error, :reverted} =
395 ActivityPub.create(%{
396 to: ["user1", "user2"],
400 "to" => ["user1", "user2"],
402 "content" => "testing"
407 assert Repo.aggregate(Activity, :count, :id) == 0
408 assert Repo.aggregate(Object, :count, :id) == 0
411 test "removes doubled 'to' recipients" do
415 ActivityPub.create(%{
416 to: ["user1", "user1", "user2"],
420 "to" => ["user1", "user1", "user2"],
422 "content" => "testing"
426 assert activity.data["to"] == ["user1", "user2"]
427 assert activity.actor == user.ap_id
428 assert activity.recipients == ["user1", "user2", user.ap_id]
431 test "increases user note count only for public activities" do
435 CommonAPI.post(User.get_cached_by_id(user.id), %{
437 "visibility" => "public"
441 CommonAPI.post(User.get_cached_by_id(user.id), %{
443 "visibility" => "unlisted"
447 CommonAPI.post(User.get_cached_by_id(user.id), %{
449 "visibility" => "private"
453 CommonAPI.post(User.get_cached_by_id(user.id), %{
455 "visibility" => "direct"
458 user = User.get_cached_by_id(user.id)
459 assert user.note_count == 2
462 test "increases replies count" do
464 user2 = insert(:user)
466 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
467 ap_id = activity.data["id"]
468 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
471 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
472 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
473 assert object.data["repliesCount"] == 1
476 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
477 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
478 assert object.data["repliesCount"] == 2
481 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
482 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
483 assert object.data["repliesCount"] == 2
486 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
487 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
488 assert object.data["repliesCount"] == 2
492 describe "fetch activities for recipients" do
493 test "retrieve the activities for certain recipients" do
494 {:ok, activity_one} = ActivityBuilder.insert(%{"to" => ["someone"]})
495 {:ok, activity_two} = ActivityBuilder.insert(%{"to" => ["someone_else"]})
496 {:ok, _activity_three} = ActivityBuilder.insert(%{"to" => ["noone"]})
498 activities = ActivityPub.fetch_activities(["someone", "someone_else"])
499 assert length(activities) == 2
500 assert activities == [activity_one, activity_two]
504 describe "fetch activities in context" do
505 test "retrieves activities that have a given context" do
506 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
507 {:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
508 {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
509 {:ok, _activity_four} = ActivityBuilder.insert(%{"type" => "Announce", "context" => "2hu"})
510 activity_five = insert(:note_activity)
513 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})
515 activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})
516 assert activities == [activity_two, activity]
520 test "doesn't return blocked activities" do
521 activity_one = insert(:note_activity)
522 activity_two = insert(:note_activity)
523 activity_three = insert(:note_activity)
525 booster = insert(:user)
526 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
529 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
531 assert Enum.member?(activities, activity_two)
532 assert Enum.member?(activities, activity_three)
533 refute Enum.member?(activities, activity_one)
535 {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
538 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
540 assert Enum.member?(activities, activity_two)
541 assert Enum.member?(activities, activity_three)
542 assert Enum.member?(activities, activity_one)
544 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})
545 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
546 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
547 activity_three = Activity.get_by_id(activity_three.id)
550 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
552 assert Enum.member?(activities, activity_two)
553 refute Enum.member?(activities, activity_three)
554 refute Enum.member?(activities, boost_activity)
555 assert Enum.member?(activities, activity_one)
558 ActivityPub.fetch_activities([], %{"blocking_user" => nil, "skip_preload" => true})
560 assert Enum.member?(activities, activity_two)
561 assert Enum.member?(activities, activity_three)
562 assert Enum.member?(activities, boost_activity)
563 assert Enum.member?(activities, activity_one)
566 test "doesn't return transitive interactions concerning blocked users" do
567 blocker = insert(:user)
568 blockee = insert(:user)
569 friend = insert(:user)
571 {:ok, _user_relationship} = User.block(blocker, blockee)
573 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
575 {:ok, activity_two} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"})
577 {:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
579 {:ok, activity_four} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"})
581 activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
583 assert Enum.member?(activities, activity_one)
584 refute Enum.member?(activities, activity_two)
585 refute Enum.member?(activities, activity_three)
586 refute Enum.member?(activities, activity_four)
589 test "doesn't return announce activities concerning blocked users" do
590 blocker = insert(:user)
591 blockee = insert(:user)
592 friend = insert(:user)
594 {:ok, _user_relationship} = User.block(blocker, blockee)
596 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
598 {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
600 {:ok, activity_three, _} = CommonAPI.repeat(activity_two.id, friend)
603 ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
604 |> Enum.map(fn act -> act.id end)
606 assert Enum.member?(activities, activity_one.id)
607 refute Enum.member?(activities, activity_two.id)
608 refute Enum.member?(activities, activity_three.id)
611 test "doesn't return activities from blocked domains" do
612 domain = "dogwhistle.zone"
613 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
614 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
615 activity = insert(:note_activity, %{note: note})
617 {:ok, user} = User.block_domain(user, domain)
620 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
622 refute activity in activities
624 followed_user = insert(:user)
625 ActivityPub.follow(user, followed_user)
626 {:ok, repeat_activity, _} = CommonAPI.repeat(activity.id, followed_user)
629 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
631 refute repeat_activity in activities
634 test "does return activities from followed users on blocked domains" do
635 domain = "meanies.social"
636 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
637 blocker = insert(:user)
639 {:ok, blocker} = User.follow(blocker, domain_user)
640 {:ok, blocker} = User.block_domain(blocker, domain)
642 assert User.following?(blocker, domain_user)
643 assert User.blocks_domain?(blocker, domain_user)
644 refute User.blocks?(blocker, domain_user)
646 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
647 activity = insert(:note_activity, %{note: note})
650 ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true})
652 assert activity in activities
654 # And check that if the guy we DO follow boosts someone else from their domain,
655 # that should be hidden
656 another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"})
657 bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}})
658 bad_activity = insert(:note_activity, %{note: bad_note})
659 {:ok, repeat_activity, _} = CommonAPI.repeat(bad_activity.id, domain_user)
662 ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true})
664 refute repeat_activity in activities
667 test "doesn't return muted activities" do
668 activity_one = insert(:note_activity)
669 activity_two = insert(:note_activity)
670 activity_three = insert(:note_activity)
672 booster = insert(:user)
674 activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
675 {:ok, _user_relationships} = User.mute(user, activity_one_actor)
678 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
680 assert Enum.member?(activities, activity_two)
681 assert Enum.member?(activities, activity_three)
682 refute Enum.member?(activities, activity_one)
684 # Calling with 'with_muted' will deliver muted activities, too.
686 ActivityPub.fetch_activities([], %{
687 "muting_user" => user,
688 "with_muted" => true,
689 "skip_preload" => true
692 assert Enum.member?(activities, activity_two)
693 assert Enum.member?(activities, activity_three)
694 assert Enum.member?(activities, activity_one)
696 {:ok, _user_mute} = User.unmute(user, activity_one_actor)
699 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
701 assert Enum.member?(activities, activity_two)
702 assert Enum.member?(activities, activity_three)
703 assert Enum.member?(activities, activity_one)
705 activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
706 {:ok, _user_relationships} = User.mute(user, activity_three_actor)
707 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
708 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
709 activity_three = Activity.get_by_id(activity_three.id)
712 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
714 assert Enum.member?(activities, activity_two)
715 refute Enum.member?(activities, activity_three)
716 refute Enum.member?(activities, boost_activity)
717 assert Enum.member?(activities, activity_one)
719 activities = ActivityPub.fetch_activities([], %{"muting_user" => nil, "skip_preload" => true})
721 assert Enum.member?(activities, activity_two)
722 assert Enum.member?(activities, activity_three)
723 assert Enum.member?(activities, boost_activity)
724 assert Enum.member?(activities, activity_one)
727 test "doesn't return thread muted activities" do
729 _activity_one = insert(:note_activity)
730 note_two = insert(:note, data: %{"context" => "suya.."})
731 activity_two = insert(:note_activity, note: note_two)
733 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
735 assert [_activity_one] = ActivityPub.fetch_activities([], %{"muting_user" => user})
738 test "returns thread muted activities when with_muted is set" do
740 _activity_one = insert(:note_activity)
741 note_two = insert(:note, data: %{"context" => "suya.."})
742 activity_two = insert(:note_activity, note: note_two)
744 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
746 assert [_activity_two, _activity_one] =
747 ActivityPub.fetch_activities([], %{"muting_user" => user, "with_muted" => true})
750 test "does include announces on request" do
751 activity_three = insert(:note_activity)
753 booster = insert(:user)
755 {:ok, user} = User.follow(user, booster)
757 {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster)
759 [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])
761 assert announce_activity.id == announce.id
764 test "excludes reblogs on request" do
766 {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
767 {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
769 [activity] = ActivityPub.fetch_user_activities(user, nil, %{"exclude_reblogs" => "true"})
771 assert activity == expected_activity
774 describe "public fetch activities" do
775 test "doesn't retrieve unlisted activities" do
778 {:ok, _unlisted_activity} =
779 CommonAPI.post(user, %{"status" => "yeah", "visibility" => "unlisted"})
781 {:ok, listed_activity} = CommonAPI.post(user, %{"status" => "yeah"})
783 [activity] = ActivityPub.fetch_public_activities()
785 assert activity == listed_activity
788 test "retrieves public activities" do
789 _activities = ActivityPub.fetch_public_activities()
791 %{public: public} = ActivityBuilder.public_and_non_public()
793 activities = ActivityPub.fetch_public_activities()
794 assert length(activities) == 1
795 assert Enum.at(activities, 0) == public
798 test "retrieves a maximum of 20 activities" do
799 ActivityBuilder.insert_list(10)
800 expected_activities = ActivityBuilder.insert_list(20)
802 activities = ActivityPub.fetch_public_activities()
804 assert collect_ids(activities) == collect_ids(expected_activities)
805 assert length(activities) == 20
808 test "retrieves ids starting from a since_id" do
809 activities = ActivityBuilder.insert_list(30)
810 expected_activities = ActivityBuilder.insert_list(10)
811 since_id = List.last(activities).id
813 activities = ActivityPub.fetch_public_activities(%{"since_id" => since_id})
815 assert collect_ids(activities) == collect_ids(expected_activities)
816 assert length(activities) == 10
819 test "retrieves ids up to max_id" do
820 ActivityBuilder.insert_list(10)
821 expected_activities = ActivityBuilder.insert_list(20)
825 |> ActivityBuilder.insert_list()
828 activities = ActivityPub.fetch_public_activities(%{"max_id" => max_id})
830 assert length(activities) == 20
831 assert collect_ids(activities) == collect_ids(expected_activities)
834 test "paginates via offset/limit" do
835 _first_part_activities = ActivityBuilder.insert_list(10)
836 second_part_activities = ActivityBuilder.insert_list(10)
838 later_activities = ActivityBuilder.insert_list(10)
841 ActivityPub.fetch_public_activities(%{"page" => "2", "page_size" => "20"}, :offset)
843 assert length(activities) == 20
845 assert collect_ids(activities) ==
846 collect_ids(second_part_activities) ++ collect_ids(later_activities)
849 test "doesn't return reblogs for users for whom reblogs have been muted" do
850 activity = insert(:note_activity)
852 booster = insert(:user)
853 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
855 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
857 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
859 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
862 test "returns reblogs for users for whom reblogs have not been muted" do
863 activity = insert(:note_activity)
865 booster = insert(:user)
866 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
867 {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
869 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
871 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
873 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
877 describe "unreacting to an object" do
878 test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
879 Config.put([:instance, :federating], true)
881 reactor = insert(:user)
882 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
883 assert object = Object.normalize(activity)
885 {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, reactor, "🔥")
887 assert called(Federator.publish(reaction_activity))
889 {:ok, unreaction_activity, _object} =
890 ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
892 assert called(Federator.publish(unreaction_activity))
895 test "adds an undo activity to the db" do
897 reactor = insert(:user)
898 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
899 assert object = Object.normalize(activity)
901 {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, reactor, "🔥")
903 {:ok, unreaction_activity, _object} =
904 ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
906 assert unreaction_activity.actor == reactor.ap_id
907 assert unreaction_activity.data["object"] == reaction_activity.data["id"]
909 object = Object.get_by_ap_id(object.data["id"])
910 assert object.data["reaction_count"] == 0
911 assert object.data["reactions"] == []
914 test "reverts emoji unreact on error" do
915 [user, reactor] = insert_list(2, :user)
916 {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"})
917 object = Object.normalize(activity)
919 {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, reactor, "😀")
921 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
922 assert {:error, :reverted} =
923 ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
926 object = Object.get_by_ap_id(object.data["id"])
928 assert object.data["reaction_count"] == 1
929 assert object.data["reactions"] == [["😀", [reactor.ap_id]]]
933 describe "unliking" do
934 test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
935 Config.put([:instance, :federating], true)
937 note_activity = insert(:note_activity)
938 object = Object.normalize(note_activity)
941 {:ok, object} = ActivityPub.unlike(user, object)
942 refute called(Federator.publish())
944 {:ok, _like_activity} = CommonAPI.favorite(user, note_activity.id)
945 object = Object.get_by_id(object.id)
946 assert object.data["like_count"] == 1
948 {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
949 assert object.data["like_count"] == 0
951 assert called(Federator.publish(unlike_activity))
954 test "reverts unliking on error" do
955 note_activity = insert(:note_activity)
958 {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
959 object = Object.normalize(note_activity)
960 assert object.data["like_count"] == 1
962 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
963 assert {:error, :reverted} = ActivityPub.unlike(user, object)
966 assert Object.get_by_ap_id(object.data["id"]) == object
967 assert object.data["like_count"] == 1
968 assert Activity.get_by_id(like_activity.id)
971 test "unliking a previously liked object" do
972 note_activity = insert(:note_activity)
973 object = Object.normalize(note_activity)
976 # Unliking something that hasn't been liked does nothing
977 {:ok, object} = ActivityPub.unlike(user, object)
978 assert object.data["like_count"] == 0
980 {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
982 object = Object.get_by_id(object.id)
983 assert object.data["like_count"] == 1
985 {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
986 assert object.data["like_count"] == 0
988 assert Activity.get_by_id(like_activity.id) == nil
989 assert note_activity.actor in unlike_activity.recipients
993 describe "announcing an object" do
994 test "adds an announce activity to the db" do
995 note_activity = insert(:note_activity)
996 object = Object.normalize(note_activity)
999 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
1000 assert object.data["announcement_count"] == 1
1001 assert object.data["announcements"] == [user.ap_id]
1003 assert announce_activity.data["to"] == [
1004 User.ap_followers(user),
1005 note_activity.data["actor"]
1008 assert announce_activity.data["object"] == object.data["id"]
1009 assert announce_activity.data["actor"] == user.ap_id
1010 assert announce_activity.data["context"] == object.data["context"]
1013 test "reverts annouce from object on error" do
1014 note_activity = insert(:note_activity)
1015 object = Object.normalize(note_activity)
1016 user = insert(:user)
1018 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1019 assert {:error, :reverted} = ActivityPub.announce(user, object)
1022 reloaded_object = Object.get_by_ap_id(object.data["id"])
1023 assert reloaded_object == object
1024 refute reloaded_object.data["announcement_count"]
1025 refute reloaded_object.data["announcements"]
1029 describe "announcing a private object" do
1030 test "adds an announce activity to the db if the audience is not widened" do
1031 user = insert(:user)
1032 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1033 object = Object.normalize(note_activity)
1035 {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false)
1037 assert announce_activity.data["to"] == [User.ap_followers(user)]
1039 assert announce_activity.data["object"] == object.data["id"]
1040 assert announce_activity.data["actor"] == user.ap_id
1041 assert announce_activity.data["context"] == object.data["context"]
1044 test "does not add an announce activity to the db if the audience is widened" do
1045 user = insert(:user)
1046 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1047 object = Object.normalize(note_activity)
1049 assert {:error, _} = ActivityPub.announce(user, object, nil, true, true)
1052 test "does not add an announce activity to the db if the announcer is not the author" do
1053 user = insert(:user)
1054 announcer = insert(:user)
1055 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1056 object = Object.normalize(note_activity)
1058 assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false)
1062 describe "unannouncing an object" do
1063 test "unannouncing a previously announced object" do
1064 note_activity = insert(:note_activity)
1065 object = Object.normalize(note_activity)
1066 user = insert(:user)
1068 # Unannouncing an object that is not announced does nothing
1069 {:ok, object} = ActivityPub.unannounce(user, object)
1070 refute object.data["announcement_count"]
1072 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
1073 assert object.data["announcement_count"] == 1
1075 {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
1076 assert object.data["announcement_count"] == 0
1078 assert unannounce_activity.data["to"] == [
1079 User.ap_followers(user),
1080 object.data["actor"]
1083 assert unannounce_activity.data["type"] == "Undo"
1084 assert unannounce_activity.data["object"] == announce_activity.data
1085 assert unannounce_activity.data["actor"] == user.ap_id
1086 assert unannounce_activity.data["context"] == announce_activity.data["context"]
1088 assert Activity.get_by_id(announce_activity.id) == nil
1091 test "reverts unannouncing on error" do
1092 note_activity = insert(:note_activity)
1093 object = Object.normalize(note_activity)
1094 user = insert(:user)
1096 {:ok, _announce_activity, object} = ActivityPub.announce(user, object)
1097 assert object.data["announcement_count"] == 1
1099 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1100 assert {:error, :reverted} = ActivityPub.unannounce(user, object)
1103 object = Object.get_by_ap_id(object.data["id"])
1104 assert object.data["announcement_count"] == 1
1108 describe "uploading files" do
1109 test "copies the file to the configured folder" do
1110 file = %Plug.Upload{
1111 content_type: "image/jpg",
1112 path: Path.absname("test/fixtures/image.jpg"),
1113 filename: "an_image.jpg"
1116 {:ok, %Object{} = object} = ActivityPub.upload(file)
1117 assert object.data["name"] == "an_image.jpg"
1120 test "works with base64 encoded images" do
1125 {:ok, %Object{}} = ActivityPub.upload(file)
1129 describe "fetch the latest Follow" do
1130 test "fetches the latest Follow activity" do
1131 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1132 follower = Repo.get_by(User, ap_id: activity.data["actor"])
1133 followed = Repo.get_by(User, ap_id: activity.data["object"])
1135 assert activity == Utils.fetch_latest_follow(follower, followed)
1139 describe "following / unfollowing" do
1140 test "it reverts follow activity" do
1141 follower = insert(:user)
1142 followed = insert(:user)
1144 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1145 assert {:error, :reverted} = ActivityPub.follow(follower, followed)
1148 assert Repo.aggregate(Activity, :count, :id) == 0
1149 assert Repo.aggregate(Object, :count, :id) == 0
1152 test "it reverts unfollow activity" do
1153 follower = insert(:user)
1154 followed = insert(:user)
1156 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1158 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1159 assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1162 activity = Activity.get_by_id(follow_activity.id)
1163 assert activity.data["type"] == "Follow"
1164 assert activity.data["actor"] == follower.ap_id
1166 assert activity.data["object"] == followed.ap_id
1169 test "creates a follow activity" do
1170 follower = insert(:user)
1171 followed = insert(:user)
1173 {:ok, activity} = ActivityPub.follow(follower, followed)
1174 assert activity.data["type"] == "Follow"
1175 assert activity.data["actor"] == follower.ap_id
1176 assert activity.data["object"] == followed.ap_id
1179 test "creates an undo activity for the last follow" do
1180 follower = insert(:user)
1181 followed = insert(:user)
1183 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1184 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1186 assert activity.data["type"] == "Undo"
1187 assert activity.data["actor"] == follower.ap_id
1189 embedded_object = activity.data["object"]
1190 assert is_map(embedded_object)
1191 assert embedded_object["type"] == "Follow"
1192 assert embedded_object["object"] == followed.ap_id
1193 assert embedded_object["id"] == follow_activity.data["id"]
1196 test "creates an undo activity for a pending follow request" do
1197 follower = insert(:user)
1198 followed = insert(:user, %{locked: true})
1200 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1201 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1203 assert activity.data["type"] == "Undo"
1204 assert activity.data["actor"] == follower.ap_id
1206 embedded_object = activity.data["object"]
1207 assert is_map(embedded_object)
1208 assert embedded_object["type"] == "Follow"
1209 assert embedded_object["object"] == followed.ap_id
1210 assert embedded_object["id"] == follow_activity.data["id"]
1214 describe "blocking / unblocking" do
1215 test "reverts block activity on error" do
1216 [blocker, blocked] = insert_list(2, :user)
1218 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1219 assert {:error, :reverted} = ActivityPub.block(blocker, blocked)
1222 assert Repo.aggregate(Activity, :count, :id) == 0
1223 assert Repo.aggregate(Object, :count, :id) == 0
1226 test "creates a block activity" do
1227 blocker = insert(:user)
1228 blocked = insert(:user)
1230 {:ok, activity} = ActivityPub.block(blocker, blocked)
1232 assert activity.data["type"] == "Block"
1233 assert activity.data["actor"] == blocker.ap_id
1234 assert activity.data["object"] == blocked.ap_id
1237 test "reverts unblock activity on error" do
1238 [blocker, blocked] = insert_list(2, :user)
1239 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1241 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1242 assert {:error, :reverted} = ActivityPub.unblock(blocker, blocked)
1245 assert block_activity.data["type"] == "Block"
1246 assert block_activity.data["actor"] == blocker.ap_id
1248 assert Repo.aggregate(Activity, :count, :id) == 1
1249 assert Repo.aggregate(Object, :count, :id) == 1
1252 test "creates an undo activity for the last block" do
1253 blocker = insert(:user)
1254 blocked = insert(:user)
1256 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1257 {:ok, activity} = ActivityPub.unblock(blocker, blocked)
1259 assert activity.data["type"] == "Undo"
1260 assert activity.data["actor"] == blocker.ap_id
1262 embedded_object = activity.data["object"]
1263 assert is_map(embedded_object)
1264 assert embedded_object["type"] == "Block"
1265 assert embedded_object["object"] == blocked.ap_id
1266 assert embedded_object["id"] == block_activity.data["id"]
1270 describe "deletion" do
1271 setup do: clear_config([:instance, :rewrite_policy])
1273 test "it reverts deletion on error" do
1274 note = insert(:note_activity)
1275 object = Object.normalize(note)
1277 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1278 assert {:error, :reverted} = ActivityPub.delete(object)
1281 assert Repo.aggregate(Activity, :count, :id) == 1
1282 assert Repo.get(Object, object.id) == object
1283 assert Activity.get_by_id(note.id) == note
1286 test "it creates a delete activity and deletes the original object" do
1287 note = insert(:note_activity)
1288 object = Object.normalize(note)
1289 {:ok, delete} = ActivityPub.delete(object)
1291 assert delete.data["type"] == "Delete"
1292 assert delete.data["actor"] == note.data["actor"]
1293 assert delete.data["object"] == object.data["id"]
1295 assert Activity.get_by_id(delete.id) != nil
1297 assert Repo.get(Object, object.id).data["type"] == "Tombstone"
1300 test "it doesn't fail when an activity was already deleted" do
1301 {:ok, delete} = insert(:note_activity) |> Object.normalize() |> ActivityPub.delete()
1303 assert {:ok, ^delete} = delete |> Object.normalize() |> ActivityPub.delete()
1306 test "decrements user note count only for public activities" do
1307 user = insert(:user, note_count: 10)
1310 CommonAPI.post(User.get_cached_by_id(user.id), %{
1312 "visibility" => "public"
1316 CommonAPI.post(User.get_cached_by_id(user.id), %{
1318 "visibility" => "unlisted"
1322 CommonAPI.post(User.get_cached_by_id(user.id), %{
1324 "visibility" => "private"
1328 CommonAPI.post(User.get_cached_by_id(user.id), %{
1330 "visibility" => "direct"
1333 {:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
1334 {:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
1335 {:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
1336 {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
1338 user = User.get_cached_by_id(user.id)
1339 assert user.note_count == 10
1342 test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
1343 user = insert(:user)
1344 note = insert(:note_activity)
1345 object = Object.normalize(note)
1351 "actor" => object.data["actor"],
1352 "id" => object.data["id"],
1353 "to" => [user.ap_id],
1357 |> Object.update_and_set_cache()
1359 {:ok, delete} = ActivityPub.delete(object)
1361 assert user.ap_id in delete.data["to"]
1364 test "decreases reply count" do
1365 user = insert(:user)
1366 user2 = insert(:user)
1368 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
1369 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
1370 ap_id = activity.data["id"]
1372 {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
1373 {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
1374 {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
1375 {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
1377 _ = CommonAPI.delete(direct_reply.id, user2)
1378 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1379 assert object.data["repliesCount"] == 2
1381 _ = CommonAPI.delete(private_reply.id, user2)
1382 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1383 assert object.data["repliesCount"] == 2
1385 _ = CommonAPI.delete(public_reply.id, user2)
1386 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1387 assert object.data["repliesCount"] == 1
1389 _ = CommonAPI.delete(unlisted_reply.id, user2)
1390 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1391 assert object.data["repliesCount"] == 0
1394 test "it passes delete activity through MRF before deleting the object" do
1395 Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy)
1397 note = insert(:note_activity)
1398 object = Object.normalize(note)
1400 {:error, {:reject, _}} = ActivityPub.delete(object)
1402 assert Activity.get_by_id(note.id)
1403 assert Repo.get(Object, object.id).data["type"] == object.data["type"]
1407 describe "timeline post-processing" do
1408 test "it filters broken threads" do
1409 user1 = insert(:user)
1410 user2 = insert(:user)
1411 user3 = insert(:user)
1413 {:ok, user1} = User.follow(user1, user3)
1414 assert User.following?(user1, user3)
1416 {:ok, user2} = User.follow(user2, user3)
1417 assert User.following?(user2, user3)
1419 {:ok, user3} = User.follow(user3, user2)
1420 assert User.following?(user3, user2)
1422 {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"})
1424 {:ok, private_activity_1} =
1425 CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
1427 {:ok, private_activity_2} =
1428 CommonAPI.post(user2, %{
1430 "visibility" => "private",
1431 "in_reply_to_status_id" => private_activity_1.id
1434 {:ok, private_activity_3} =
1435 CommonAPI.post(user3, %{
1437 "visibility" => "private",
1438 "in_reply_to_status_id" => private_activity_2.id
1442 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1443 |> Enum.map(fn a -> a.id end)
1445 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1447 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1449 assert length(activities) == 3
1452 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{"user" => user1})
1453 |> Enum.map(fn a -> a.id end)
1455 assert [public_activity.id, private_activity_1.id] == activities
1456 assert length(activities) == 2
1460 describe "update" do
1461 setup do: clear_config([:instance, :max_pinned_statuses])
1463 test "it creates an update activity with the new user data" do
1464 user = insert(:user)
1465 {:ok, user} = User.ensure_keys_present(user)
1466 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
1469 ActivityPub.update(%{
1470 actor: user_data["id"],
1471 to: [user.follower_address],
1476 assert update.data["actor"] == user.ap_id
1477 assert update.data["to"] == [user.follower_address]
1478 assert embedded_object = update.data["object"]
1479 assert embedded_object["id"] == user_data["id"]
1480 assert embedded_object["type"] == user_data["type"]
1484 test "returned pinned statuses" do
1485 Config.put([:instance, :max_pinned_statuses], 3)
1486 user = insert(:user)
1488 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
1489 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
1490 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
1492 CommonAPI.pin(activity_one.id, user)
1493 user = refresh_record(user)
1495 CommonAPI.pin(activity_two.id, user)
1496 user = refresh_record(user)
1498 CommonAPI.pin(activity_three.id, user)
1499 user = refresh_record(user)
1501 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
1503 assert 3 = length(activities)
1506 describe "flag/1" do
1508 reporter = insert(:user)
1509 target_account = insert(:user)
1511 {:ok, activity} = CommonAPI.post(target_account, %{"status" => content})
1512 context = Utils.generate_context_id()
1514 reporter_ap_id = reporter.ap_id
1515 target_ap_id = target_account.ap_id
1516 activity_ap_id = activity.data["id"]
1518 activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1524 target_account: target_account,
1525 reported_activity: activity,
1527 activity_ap_id: activity_ap_id,
1528 activity_with_object: activity_with_object,
1529 reporter_ap_id: reporter_ap_id,
1530 target_ap_id: target_ap_id
1534 test "it can create a Flag activity",
1538 target_account: target_account,
1539 reported_activity: reported_activity,
1541 activity_ap_id: activity_ap_id,
1542 activity_with_object: activity_with_object,
1543 reporter_ap_id: reporter_ap_id,
1544 target_ap_id: target_ap_id
1546 assert {:ok, activity} =
1550 account: target_account,
1551 statuses: [reported_activity],
1557 "id" => activity_ap_id,
1558 "content" => content,
1559 "published" => activity_with_object.object.data["published"],
1560 "actor" => AccountView.render("show.json", %{user: target_account})
1564 actor: ^reporter_ap_id,
1567 "content" => ^content,
1568 "context" => ^context,
1569 "object" => [^target_ap_id, ^note_obj]
1574 test_with_mock "strips status data from Flag, before federating it",
1578 target_account: target_account,
1579 reported_activity: reported_activity,
1589 account: target_account,
1590 statuses: [reported_activity],
1595 put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]])
1597 assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1601 test "fetch_activities/2 returns activities addressed to a list " do
1602 user = insert(:user)
1603 member = insert(:user)
1604 {:ok, list} = Pleroma.List.create("foo", user)
1605 {:ok, list} = Pleroma.List.follow(list, member)
1608 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1610 activity = Repo.preload(activity, :bookmark)
1611 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1613 assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
1617 File.read!("test/fixtures/avatar_data_uri")
1620 describe "fetch_activities_bounded" do
1621 test "fetches private posts for followed users" do
1622 user = insert(:user)
1625 CommonAPI.post(user, %{
1626 "status" => "thought I looked cute might delete later :3",
1627 "visibility" => "private"
1630 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1631 assert result.id == activity.id
1634 test "fetches only public posts for other users" do
1635 user = insert(:user)
1636 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
1638 {:ok, _private_activity} =
1639 CommonAPI.post(user, %{
1640 "status" => "why is tenshi eating a corndog so cute?",
1641 "visibility" => "private"
1644 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1645 assert result.id == activity.id
1649 describe "fetch_follow_information_for_user" do
1650 test "syncronizes following/followers counters" do
1654 follower_address: "http://localhost:4001/users/fuser2/followers",
1655 following_address: "http://localhost:4001/users/fuser2/following"
1658 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1659 assert info.follower_count == 527
1660 assert info.following_count == 267
1663 test "detects hidden followers" do
1666 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1667 %Tesla.Env{status: 403, body: ""}
1670 apply(HttpRequestMock, :request, [env])
1677 follower_address: "http://localhost:4001/users/masto_closed/followers",
1678 following_address: "http://localhost:4001/users/masto_closed/following"
1681 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1682 assert follow_info.hide_followers == true
1683 assert follow_info.hide_follows == false
1686 test "detects hidden follows" do
1689 "http://localhost:4001/users/masto_closed/following?page=1" ->
1690 %Tesla.Env{status: 403, body: ""}
1693 apply(HttpRequestMock, :request, [env])
1700 follower_address: "http://localhost:4001/users/masto_closed/followers",
1701 following_address: "http://localhost:4001/users/masto_closed/following"
1704 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1705 assert follow_info.hide_followers == false
1706 assert follow_info.hide_follows == true
1709 test "detects hidden follows/followers for friendica" do
1713 follower_address: "http://localhost:8080/followers/fuser3",
1714 following_address: "http://localhost:8080/following/fuser3"
1717 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1718 assert follow_info.hide_followers == true
1719 assert follow_info.follower_count == 296
1720 assert follow_info.following_count == 32
1721 assert follow_info.hide_follows == true
1724 test "doesn't crash when follower and following counters are hidden" do
1727 "http://localhost:4001/users/masto_hidden_counters/following" ->
1729 "@context" => "https://www.w3.org/ns/activitystreams",
1730 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1733 "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1734 %Tesla.Env{status: 403, body: ""}
1736 "http://localhost:4001/users/masto_hidden_counters/followers" ->
1738 "@context" => "https://www.w3.org/ns/activitystreams",
1739 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1742 "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1743 %Tesla.Env{status: 403, body: ""}
1750 follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1751 following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1754 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1756 assert follow_info.hide_followers == true
1757 assert follow_info.follower_count == 0
1758 assert follow_info.hide_follows == true
1759 assert follow_info.following_count == 0
1763 describe "fetch_favourites/3" do
1764 test "returns a favourite activities sorted by adds to favorite" do
1765 user = insert(:user)
1766 other_user = insert(:user)
1767 user1 = insert(:user)
1768 user2 = insert(:user)
1769 {:ok, a1} = CommonAPI.post(user1, %{"status" => "bla"})
1770 {:ok, _a2} = CommonAPI.post(user2, %{"status" => "traps are happy"})
1771 {:ok, a3} = CommonAPI.post(user2, %{"status" => "Trees Are "})
1772 {:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "})
1773 {:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "})
1775 {:ok, _} = CommonAPI.favorite(user, a4.id)
1776 {:ok, _} = CommonAPI.favorite(other_user, a3.id)
1777 {:ok, _} = CommonAPI.favorite(user, a3.id)
1778 {:ok, _} = CommonAPI.favorite(other_user, a5.id)
1779 {:ok, _} = CommonAPI.favorite(user, a5.id)
1780 {:ok, _} = CommonAPI.favorite(other_user, a4.id)
1781 {:ok, _} = CommonAPI.favorite(user, a1.id)
1782 {:ok, _} = CommonAPI.favorite(other_user, a1.id)
1783 result = ActivityPub.fetch_favourites(user)
1785 assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1787 result = ActivityPub.fetch_favourites(user, %{"limit" => 2})
1788 assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1792 describe "Move activity" do
1794 %{ap_id: old_ap_id} = old_user = insert(:user)
1795 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1796 follower = insert(:user)
1797 follower_move_opted_out = insert(:user, allow_following_move: false)
1799 User.follow(follower, old_user)
1800 User.follow(follower_move_opted_out, old_user)
1802 assert User.following?(follower, old_user)
1803 assert User.following?(follower_move_opted_out, old_user)
1805 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1810 "actor" => ^old_ap_id,
1811 "object" => ^old_ap_id,
1812 "target" => ^new_ap_id,
1819 "op" => "move_following",
1820 "origin_id" => old_user.id,
1821 "target_id" => new_user.id
1824 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1826 Pleroma.Workers.BackgroundWorker.perform(params, nil)
1828 refute User.following?(follower, old_user)
1829 assert User.following?(follower, new_user)
1831 assert User.following?(follower_move_opted_out, old_user)
1832 refute User.following?(follower_move_opted_out, new_user)
1834 activity = %Activity{activity | object: nil}
1836 assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1838 assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1841 test "old user must be in the new user's `also_known_as` list" do
1842 old_user = insert(:user)
1843 new_user = insert(:user)
1845 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1846 ActivityPub.move(old_user, new_user)
1850 test "doesn't retrieve replies activities with exclude_replies" do
1851 user = insert(:user)
1853 {:ok, activity} = CommonAPI.post(user, %{"status" => "yeah"})
1856 CommonAPI.post(user, %{"status" => "yeah", "in_reply_to_status_id" => activity.id})
1858 [result] = ActivityPub.fetch_public_activities(%{"exclude_replies" => "true"})
1860 assert result.id == activity.id
1862 assert length(ActivityPub.fetch_public_activities()) == 2
1865 describe "replies filtering with public messages" do
1866 setup :public_messages
1868 test "public timeline", %{users: %{u1: user}} do
1871 |> Map.put("type", ["Create", "Announce"])
1872 |> Map.put("local_only", false)
1873 |> Map.put("blocking_user", user)
1874 |> Map.put("muting_user", user)
1875 |> Map.put("reply_filtering_user", user)
1876 |> ActivityPub.fetch_public_activities()
1877 |> Enum.map(& &1.id)
1879 assert length(activities_ids) == 16
1882 test "public timeline with reply_visibility `following`", %{
1888 activities: activities
1892 |> Map.put("type", ["Create", "Announce"])
1893 |> Map.put("local_only", false)
1894 |> Map.put("blocking_user", user)
1895 |> Map.put("muting_user", user)
1896 |> Map.put("reply_visibility", "following")
1897 |> Map.put("reply_filtering_user", user)
1898 |> ActivityPub.fetch_public_activities()
1899 |> Enum.map(& &1.id)
1901 assert length(activities_ids) == 14
1904 Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
1906 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1909 test "public timeline with reply_visibility `self`", %{
1915 activities: activities
1919 |> Map.put("type", ["Create", "Announce"])
1920 |> Map.put("local_only", false)
1921 |> Map.put("blocking_user", user)
1922 |> Map.put("muting_user", user)
1923 |> Map.put("reply_visibility", "self")
1924 |> Map.put("reply_filtering_user", user)
1925 |> ActivityPub.fetch_public_activities()
1926 |> Enum.map(& &1.id)
1928 assert length(activities_ids) == 10
1929 visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
1930 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1933 test "home timeline", %{
1935 activities: activities,
1943 |> Map.put("type", ["Create", "Announce"])
1944 |> Map.put("blocking_user", user)
1945 |> Map.put("muting_user", user)
1946 |> Map.put("user", user)
1947 |> Map.put("reply_filtering_user", user)
1950 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1951 |> Enum.map(& &1.id)
1953 assert length(activities_ids) == 13
1968 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1971 test "home timeline with reply_visibility `following`", %{
1973 activities: activities,
1981 |> Map.put("type", ["Create", "Announce"])
1982 |> Map.put("blocking_user", user)
1983 |> Map.put("muting_user", user)
1984 |> Map.put("user", user)
1985 |> Map.put("reply_visibility", "following")
1986 |> Map.put("reply_filtering_user", user)
1989 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1990 |> Enum.map(& &1.id)
1992 assert length(activities_ids) == 11
2007 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2010 test "home timeline with reply_visibility `self`", %{
2012 activities: activities,
2020 |> Map.put("type", ["Create", "Announce"])
2021 |> Map.put("blocking_user", user)
2022 |> Map.put("muting_user", user)
2023 |> Map.put("user", user)
2024 |> Map.put("reply_visibility", "self")
2025 |> Map.put("reply_filtering_user", user)
2028 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2029 |> Enum.map(& &1.id)
2031 assert length(activities_ids) == 9
2044 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2048 describe "replies filtering with private messages" do
2049 setup :private_messages
2051 test "public timeline", %{users: %{u1: user}} do
2054 |> Map.put("type", ["Create", "Announce"])
2055 |> Map.put("local_only", false)
2056 |> Map.put("blocking_user", user)
2057 |> Map.put("muting_user", user)
2058 |> Map.put("user", user)
2059 |> ActivityPub.fetch_public_activities()
2060 |> Enum.map(& &1.id)
2062 assert activities_ids == []
2065 test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2068 |> Map.put("type", ["Create", "Announce"])
2069 |> Map.put("local_only", false)
2070 |> Map.put("blocking_user", user)
2071 |> Map.put("muting_user", user)
2072 |> Map.put("reply_visibility", "following")
2073 |> Map.put("reply_filtering_user", user)
2074 |> Map.put("user", user)
2075 |> ActivityPub.fetch_public_activities()
2076 |> Enum.map(& &1.id)
2078 assert activities_ids == []
2081 test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
2084 |> Map.put("type", ["Create", "Announce"])
2085 |> Map.put("local_only", false)
2086 |> Map.put("blocking_user", user)
2087 |> Map.put("muting_user", user)
2088 |> Map.put("reply_visibility", "self")
2089 |> Map.put("reply_filtering_user", user)
2090 |> Map.put("user", user)
2091 |> ActivityPub.fetch_public_activities()
2092 |> Enum.map(& &1.id)
2094 assert activities_ids == []
2097 test "home timeline", %{users: %{u1: user}} do
2100 |> Map.put("type", ["Create", "Announce"])
2101 |> Map.put("blocking_user", user)
2102 |> Map.put("muting_user", user)
2103 |> Map.put("user", user)
2106 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2107 |> Enum.map(& &1.id)
2109 assert length(activities_ids) == 12
2112 test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2115 |> Map.put("type", ["Create", "Announce"])
2116 |> Map.put("blocking_user", user)
2117 |> Map.put("muting_user", user)
2118 |> Map.put("user", user)
2119 |> Map.put("reply_visibility", "following")
2120 |> Map.put("reply_filtering_user", user)
2123 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2124 |> Enum.map(& &1.id)
2126 assert length(activities_ids) == 12
2129 test "home timeline with default reply_visibility `self`", %{
2131 activities: activities,
2139 |> Map.put("type", ["Create", "Announce"])
2140 |> Map.put("blocking_user", user)
2141 |> Map.put("muting_user", user)
2142 |> Map.put("user", user)
2143 |> Map.put("reply_visibility", "self")
2144 |> Map.put("reply_filtering_user", user)
2147 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2148 |> Enum.map(& &1.id)
2150 assert length(activities_ids) == 10
2153 Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
2155 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2159 defp public_messages(_) do
2160 [u1, u2, u3, u4] = insert_list(4, :user)
2161 {:ok, u1} = User.follow(u1, u2)
2162 {:ok, u2} = User.follow(u2, u1)
2163 {:ok, u1} = User.follow(u1, u4)
2164 {:ok, u4} = User.follow(u4, u1)
2166 {:ok, u2} = User.follow(u2, u3)
2167 {:ok, u3} = User.follow(u3, u2)
2169 {:ok, a1} = CommonAPI.post(u1, %{"status" => "Status"})
2172 CommonAPI.post(u2, %{
2173 "status" => "@#{u1.nickname} reply from u2 to u1",
2174 "in_reply_to_status_id" => a1.id
2178 CommonAPI.post(u3, %{
2179 "status" => "@#{u1.nickname} reply from u3 to u1",
2180 "in_reply_to_status_id" => a1.id
2184 CommonAPI.post(u4, %{
2185 "status" => "@#{u1.nickname} reply from u4 to u1",
2186 "in_reply_to_status_id" => a1.id
2189 {:ok, a2} = CommonAPI.post(u2, %{"status" => "Status"})
2192 CommonAPI.post(u1, %{
2193 "status" => "@#{u2.nickname} reply from u1 to u2",
2194 "in_reply_to_status_id" => a2.id
2198 CommonAPI.post(u3, %{
2199 "status" => "@#{u2.nickname} reply from u3 to u2",
2200 "in_reply_to_status_id" => a2.id
2204 CommonAPI.post(u4, %{
2205 "status" => "@#{u2.nickname} reply from u4 to u2",
2206 "in_reply_to_status_id" => a2.id
2209 {:ok, a3} = CommonAPI.post(u3, %{"status" => "Status"})
2212 CommonAPI.post(u1, %{
2213 "status" => "@#{u3.nickname} reply from u1 to u3",
2214 "in_reply_to_status_id" => a3.id
2218 CommonAPI.post(u2, %{
2219 "status" => "@#{u3.nickname} reply from u2 to u3",
2220 "in_reply_to_status_id" => a3.id
2224 CommonAPI.post(u4, %{
2225 "status" => "@#{u3.nickname} reply from u4 to u3",
2226 "in_reply_to_status_id" => a3.id
2229 {:ok, a4} = CommonAPI.post(u4, %{"status" => "Status"})
2232 CommonAPI.post(u1, %{
2233 "status" => "@#{u4.nickname} reply from u1 to u4",
2234 "in_reply_to_status_id" => a4.id
2238 CommonAPI.post(u2, %{
2239 "status" => "@#{u4.nickname} reply from u2 to u4",
2240 "in_reply_to_status_id" => a4.id
2244 CommonAPI.post(u3, %{
2245 "status" => "@#{u4.nickname} reply from u3 to u4",
2246 "in_reply_to_status_id" => a4.id
2250 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2251 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2252 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2253 u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
2254 u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
2255 u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
2258 defp private_messages(_) do
2259 [u1, u2, u3, u4] = insert_list(4, :user)
2260 {:ok, u1} = User.follow(u1, u2)
2261 {:ok, u2} = User.follow(u2, u1)
2262 {:ok, u1} = User.follow(u1, u3)
2263 {:ok, u3} = User.follow(u3, u1)
2264 {:ok, u1} = User.follow(u1, u4)
2265 {:ok, u4} = User.follow(u4, u1)
2267 {:ok, u2} = User.follow(u2, u3)
2268 {:ok, u3} = User.follow(u3, u2)
2270 {:ok, a1} = CommonAPI.post(u1, %{"status" => "Status", "visibility" => "private"})
2273 CommonAPI.post(u2, %{
2274 "status" => "@#{u1.nickname} reply from u2 to u1",
2275 "in_reply_to_status_id" => a1.id,
2276 "visibility" => "private"
2280 CommonAPI.post(u3, %{
2281 "status" => "@#{u1.nickname} reply from u3 to u1",
2282 "in_reply_to_status_id" => a1.id,
2283 "visibility" => "private"
2287 CommonAPI.post(u4, %{
2288 "status" => "@#{u1.nickname} reply from u4 to u1",
2289 "in_reply_to_status_id" => a1.id,
2290 "visibility" => "private"
2293 {:ok, a2} = CommonAPI.post(u2, %{"status" => "Status", "visibility" => "private"})
2296 CommonAPI.post(u1, %{
2297 "status" => "@#{u2.nickname} reply from u1 to u2",
2298 "in_reply_to_status_id" => a2.id,
2299 "visibility" => "private"
2303 CommonAPI.post(u3, %{
2304 "status" => "@#{u2.nickname} reply from u3 to u2",
2305 "in_reply_to_status_id" => a2.id,
2306 "visibility" => "private"
2309 {:ok, a3} = CommonAPI.post(u3, %{"status" => "Status", "visibility" => "private"})
2312 CommonAPI.post(u1, %{
2313 "status" => "@#{u3.nickname} reply from u1 to u3",
2314 "in_reply_to_status_id" => a3.id,
2315 "visibility" => "private"
2319 CommonAPI.post(u2, %{
2320 "status" => "@#{u3.nickname} reply from u2 to u3",
2321 "in_reply_to_status_id" => a3.id,
2322 "visibility" => "private"
2325 {:ok, a4} = CommonAPI.post(u4, %{"status" => "Status", "visibility" => "private"})
2328 CommonAPI.post(u1, %{
2329 "status" => "@#{u4.nickname} reply from u1 to u4",
2330 "in_reply_to_status_id" => a4.id,
2331 "visibility" => "private"
2335 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2336 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2337 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2338 u2: %{r1: r2_1.id, r2: r2_2.id},
2339 u3: %{r1: r3_1.id, r2: r3_2.id},
2343 describe "maybe_update_follow_information/1" do
2345 clear_config([:instance, :external_user_synchronization], true)
2349 ap_id: "https://gensokyo.2hu/users/raymoo",
2350 following_address: "https://gensokyo.2hu/users/following",
2351 follower_address: "https://gensokyo.2hu/users/followers",
2358 test "logs an error when it can't fetch the info", %{user: user} do
2359 assert capture_log(fn ->
2360 ActivityPub.maybe_update_follow_information(user)
2361 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2364 test "just returns the input if the user type is Application", %{
2369 |> Map.put(:type, "Application")
2371 refute capture_log(fn ->
2372 assert ^user = ActivityPub.maybe_update_follow_information(user)
2373 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2376 test "it just returns the input if the user has no following/follower addresses", %{
2381 |> Map.put(:following_address, nil)
2382 |> Map.put(:follower_address, nil)
2384 refute capture_log(fn ->
2385 assert ^user = ActivityPub.maybe_update_follow_information(user)
2386 end) =~ "Follower/Following counter update for #{user.ap_id} failed"