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 "react 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, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
887 assert called(Federator.publish(reaction_activity))
890 test "adds an emoji reaction activity to the db" do
892 reactor = insert(:user)
893 third_user = insert(:user)
894 fourth_user = insert(:user)
895 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
896 assert object = Object.normalize(activity)
898 {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
900 assert reaction_activity
902 assert reaction_activity.data["actor"] == reactor.ap_id
903 assert reaction_activity.data["type"] == "EmojiReact"
904 assert reaction_activity.data["content"] == "🔥"
905 assert reaction_activity.data["object"] == object.data["id"]
906 assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]]
907 assert reaction_activity.data["context"] == object.data["context"]
908 assert object.data["reaction_count"] == 1
909 assert object.data["reactions"] == [["🔥", [reactor.ap_id]]]
911 {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(third_user, object, "☕")
913 assert object.data["reaction_count"] == 2
914 assert object.data["reactions"] == [["🔥", [reactor.ap_id]], ["☕", [third_user.ap_id]]]
916 {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(fourth_user, object, "🔥")
918 assert object.data["reaction_count"] == 3
920 assert object.data["reactions"] == [
921 ["🔥", [fourth_user.ap_id, reactor.ap_id]],
922 ["☕", [third_user.ap_id]]
926 test "reverts emoji reaction on error" do
927 [user, reactor] = insert_list(2, :user)
929 {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"})
930 object = Object.normalize(activity)
932 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
933 assert {:error, :reverted} = ActivityPub.react_with_emoji(reactor, object, "😀")
936 object = Object.get_by_ap_id(object.data["id"])
937 refute object.data["reaction_count"]
938 refute object.data["reactions"]
942 describe "unreacting to an object" do
943 test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
944 Config.put([:instance, :federating], true)
946 reactor = insert(:user)
947 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
948 assert object = Object.normalize(activity)
950 {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
952 assert called(Federator.publish(reaction_activity))
954 {:ok, unreaction_activity, _object} =
955 ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
957 assert called(Federator.publish(unreaction_activity))
960 test "adds an undo activity to the db" do
962 reactor = insert(:user)
963 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
964 assert object = Object.normalize(activity)
966 {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
968 {:ok, unreaction_activity, _object} =
969 ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
971 assert unreaction_activity.actor == reactor.ap_id
972 assert unreaction_activity.data["object"] == reaction_activity.data["id"]
974 object = Object.get_by_ap_id(object.data["id"])
975 assert object.data["reaction_count"] == 0
976 assert object.data["reactions"] == []
979 test "reverts emoji unreact on error" do
980 [user, reactor] = insert_list(2, :user)
981 {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"})
982 object = Object.normalize(activity)
984 {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "😀")
986 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
987 assert {:error, :reverted} =
988 ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
991 object = Object.get_by_ap_id(object.data["id"])
993 assert object.data["reaction_count"] == 1
994 assert object.data["reactions"] == [["😀", [reactor.ap_id]]]
998 describe "announcing an object" do
999 test "adds an announce activity to the db" do
1000 note_activity = insert(:note_activity)
1001 object = Object.normalize(note_activity)
1002 user = insert(:user)
1004 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
1005 assert object.data["announcement_count"] == 1
1006 assert object.data["announcements"] == [user.ap_id]
1008 assert announce_activity.data["to"] == [
1009 User.ap_followers(user),
1010 note_activity.data["actor"]
1013 assert announce_activity.data["object"] == object.data["id"]
1014 assert announce_activity.data["actor"] == user.ap_id
1015 assert announce_activity.data["context"] == object.data["context"]
1018 test "reverts annouce from object on error" do
1019 note_activity = insert(:note_activity)
1020 object = Object.normalize(note_activity)
1021 user = insert(:user)
1023 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1024 assert {:error, :reverted} = ActivityPub.announce(user, object)
1027 reloaded_object = Object.get_by_ap_id(object.data["id"])
1028 assert reloaded_object == object
1029 refute reloaded_object.data["announcement_count"]
1030 refute reloaded_object.data["announcements"]
1034 describe "announcing a private object" do
1035 test "adds an announce activity to the db if the audience is not widened" do
1036 user = insert(:user)
1037 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1038 object = Object.normalize(note_activity)
1040 {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false)
1042 assert announce_activity.data["to"] == [User.ap_followers(user)]
1044 assert announce_activity.data["object"] == object.data["id"]
1045 assert announce_activity.data["actor"] == user.ap_id
1046 assert announce_activity.data["context"] == object.data["context"]
1049 test "does not add an announce activity to the db if the audience is widened" do
1050 user = insert(:user)
1051 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1052 object = Object.normalize(note_activity)
1054 assert {:error, _} = ActivityPub.announce(user, object, nil, true, true)
1057 test "does not add an announce activity to the db if the announcer is not the author" do
1058 user = insert(:user)
1059 announcer = insert(:user)
1060 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1061 object = Object.normalize(note_activity)
1063 assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false)
1067 describe "unannouncing an object" do
1068 test "unannouncing a previously announced object" do
1069 note_activity = insert(:note_activity)
1070 object = Object.normalize(note_activity)
1071 user = insert(:user)
1073 # Unannouncing an object that is not announced does nothing
1074 {:ok, object} = ActivityPub.unannounce(user, object)
1075 refute object.data["announcement_count"]
1077 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
1078 assert object.data["announcement_count"] == 1
1080 {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
1081 assert object.data["announcement_count"] == 0
1083 assert unannounce_activity.data["to"] == [
1084 User.ap_followers(user),
1085 object.data["actor"]
1088 assert unannounce_activity.data["type"] == "Undo"
1089 assert unannounce_activity.data["object"] == announce_activity.data
1090 assert unannounce_activity.data["actor"] == user.ap_id
1091 assert unannounce_activity.data["context"] == announce_activity.data["context"]
1093 assert Activity.get_by_id(announce_activity.id) == nil
1096 test "reverts unannouncing on error" do
1097 note_activity = insert(:note_activity)
1098 object = Object.normalize(note_activity)
1099 user = insert(:user)
1101 {:ok, _announce_activity, object} = ActivityPub.announce(user, object)
1102 assert object.data["announcement_count"] == 1
1104 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1105 assert {:error, :reverted} = ActivityPub.unannounce(user, object)
1108 object = Object.get_by_ap_id(object.data["id"])
1109 assert object.data["announcement_count"] == 1
1113 describe "uploading files" do
1114 test "copies the file to the configured folder" do
1115 file = %Plug.Upload{
1116 content_type: "image/jpg",
1117 path: Path.absname("test/fixtures/image.jpg"),
1118 filename: "an_image.jpg"
1121 {:ok, %Object{} = object} = ActivityPub.upload(file)
1122 assert object.data["name"] == "an_image.jpg"
1125 test "works with base64 encoded images" do
1130 {:ok, %Object{}} = ActivityPub.upload(file)
1134 describe "fetch the latest Follow" do
1135 test "fetches the latest Follow activity" do
1136 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1137 follower = Repo.get_by(User, ap_id: activity.data["actor"])
1138 followed = Repo.get_by(User, ap_id: activity.data["object"])
1140 assert activity == Utils.fetch_latest_follow(follower, followed)
1144 describe "following / unfollowing" do
1145 test "it reverts follow activity" do
1146 follower = insert(:user)
1147 followed = insert(:user)
1149 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1150 assert {:error, :reverted} = ActivityPub.follow(follower, followed)
1153 assert Repo.aggregate(Activity, :count, :id) == 0
1154 assert Repo.aggregate(Object, :count, :id) == 0
1157 test "it reverts unfollow activity" do
1158 follower = insert(:user)
1159 followed = insert(:user)
1161 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1163 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1164 assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1167 activity = Activity.get_by_id(follow_activity.id)
1168 assert activity.data["type"] == "Follow"
1169 assert activity.data["actor"] == follower.ap_id
1171 assert activity.data["object"] == followed.ap_id
1174 test "creates a follow activity" do
1175 follower = insert(:user)
1176 followed = insert(:user)
1178 {:ok, activity} = ActivityPub.follow(follower, followed)
1179 assert activity.data["type"] == "Follow"
1180 assert activity.data["actor"] == follower.ap_id
1181 assert activity.data["object"] == followed.ap_id
1184 test "creates an undo activity for the last follow" do
1185 follower = insert(:user)
1186 followed = insert(:user)
1188 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1189 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1191 assert activity.data["type"] == "Undo"
1192 assert activity.data["actor"] == follower.ap_id
1194 embedded_object = activity.data["object"]
1195 assert is_map(embedded_object)
1196 assert embedded_object["type"] == "Follow"
1197 assert embedded_object["object"] == followed.ap_id
1198 assert embedded_object["id"] == follow_activity.data["id"]
1201 test "creates an undo activity for a pending follow request" do
1202 follower = insert(:user)
1203 followed = insert(:user, %{locked: true})
1205 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1206 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1208 assert activity.data["type"] == "Undo"
1209 assert activity.data["actor"] == follower.ap_id
1211 embedded_object = activity.data["object"]
1212 assert is_map(embedded_object)
1213 assert embedded_object["type"] == "Follow"
1214 assert embedded_object["object"] == followed.ap_id
1215 assert embedded_object["id"] == follow_activity.data["id"]
1219 describe "blocking / unblocking" do
1220 test "reverts block activity on error" do
1221 [blocker, blocked] = insert_list(2, :user)
1223 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1224 assert {:error, :reverted} = ActivityPub.block(blocker, blocked)
1227 assert Repo.aggregate(Activity, :count, :id) == 0
1228 assert Repo.aggregate(Object, :count, :id) == 0
1231 test "creates a block activity" do
1232 blocker = insert(:user)
1233 blocked = insert(:user)
1235 {:ok, activity} = ActivityPub.block(blocker, blocked)
1237 assert activity.data["type"] == "Block"
1238 assert activity.data["actor"] == blocker.ap_id
1239 assert activity.data["object"] == blocked.ap_id
1242 test "reverts unblock activity on error" do
1243 [blocker, blocked] = insert_list(2, :user)
1244 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1246 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1247 assert {:error, :reverted} = ActivityPub.unblock(blocker, blocked)
1250 assert block_activity.data["type"] == "Block"
1251 assert block_activity.data["actor"] == blocker.ap_id
1253 assert Repo.aggregate(Activity, :count, :id) == 1
1254 assert Repo.aggregate(Object, :count, :id) == 1
1257 test "creates an undo activity for the last block" do
1258 blocker = insert(:user)
1259 blocked = insert(:user)
1261 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1262 {:ok, activity} = ActivityPub.unblock(blocker, blocked)
1264 assert activity.data["type"] == "Undo"
1265 assert activity.data["actor"] == blocker.ap_id
1267 embedded_object = activity.data["object"]
1268 assert is_map(embedded_object)
1269 assert embedded_object["type"] == "Block"
1270 assert embedded_object["object"] == blocked.ap_id
1271 assert embedded_object["id"] == block_activity.data["id"]
1275 describe "deletion" do
1276 setup do: clear_config([:instance, :rewrite_policy])
1278 test "it reverts deletion on error" do
1279 note = insert(:note_activity)
1280 object = Object.normalize(note)
1282 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1283 assert {:error, :reverted} = ActivityPub.delete(object)
1286 assert Repo.aggregate(Activity, :count, :id) == 1
1287 assert Repo.get(Object, object.id) == object
1288 assert Activity.get_by_id(note.id) == note
1291 test "it creates a delete activity and deletes the original object" do
1292 note = insert(:note_activity)
1293 object = Object.normalize(note)
1294 {:ok, delete} = ActivityPub.delete(object)
1296 assert delete.data["type"] == "Delete"
1297 assert delete.data["actor"] == note.data["actor"]
1298 assert delete.data["object"] == object.data["id"]
1300 assert Activity.get_by_id(delete.id) != nil
1302 assert Repo.get(Object, object.id).data["type"] == "Tombstone"
1305 test "it doesn't fail when an activity was already deleted" do
1306 {:ok, delete} = insert(:note_activity) |> Object.normalize() |> ActivityPub.delete()
1308 assert {:ok, ^delete} = delete |> Object.normalize() |> ActivityPub.delete()
1311 test "decrements user note count only for public activities" do
1312 user = insert(:user, note_count: 10)
1315 CommonAPI.post(User.get_cached_by_id(user.id), %{
1317 "visibility" => "public"
1321 CommonAPI.post(User.get_cached_by_id(user.id), %{
1323 "visibility" => "unlisted"
1327 CommonAPI.post(User.get_cached_by_id(user.id), %{
1329 "visibility" => "private"
1333 CommonAPI.post(User.get_cached_by_id(user.id), %{
1335 "visibility" => "direct"
1338 {:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
1339 {:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
1340 {:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
1341 {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
1343 user = User.get_cached_by_id(user.id)
1344 assert user.note_count == 10
1347 test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
1348 user = insert(:user)
1349 note = insert(:note_activity)
1350 object = Object.normalize(note)
1356 "actor" => object.data["actor"],
1357 "id" => object.data["id"],
1358 "to" => [user.ap_id],
1362 |> Object.update_and_set_cache()
1364 {:ok, delete} = ActivityPub.delete(object)
1366 assert user.ap_id in delete.data["to"]
1369 test "decreases reply count" do
1370 user = insert(:user)
1371 user2 = insert(:user)
1373 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
1374 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
1375 ap_id = activity.data["id"]
1377 {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
1378 {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
1379 {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
1380 {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
1382 _ = CommonAPI.delete(direct_reply.id, user2)
1383 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1384 assert object.data["repliesCount"] == 2
1386 _ = CommonAPI.delete(private_reply.id, user2)
1387 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1388 assert object.data["repliesCount"] == 2
1390 _ = CommonAPI.delete(public_reply.id, user2)
1391 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1392 assert object.data["repliesCount"] == 1
1394 _ = CommonAPI.delete(unlisted_reply.id, user2)
1395 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1396 assert object.data["repliesCount"] == 0
1399 test "it passes delete activity through MRF before deleting the object" do
1400 Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy)
1402 note = insert(:note_activity)
1403 object = Object.normalize(note)
1405 {:error, {:reject, _}} = ActivityPub.delete(object)
1407 assert Activity.get_by_id(note.id)
1408 assert Repo.get(Object, object.id).data["type"] == object.data["type"]
1412 describe "timeline post-processing" do
1413 test "it filters broken threads" do
1414 user1 = insert(:user)
1415 user2 = insert(:user)
1416 user3 = insert(:user)
1418 {:ok, user1} = User.follow(user1, user3)
1419 assert User.following?(user1, user3)
1421 {:ok, user2} = User.follow(user2, user3)
1422 assert User.following?(user2, user3)
1424 {:ok, user3} = User.follow(user3, user2)
1425 assert User.following?(user3, user2)
1427 {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"})
1429 {:ok, private_activity_1} =
1430 CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
1432 {:ok, private_activity_2} =
1433 CommonAPI.post(user2, %{
1435 "visibility" => "private",
1436 "in_reply_to_status_id" => private_activity_1.id
1439 {:ok, private_activity_3} =
1440 CommonAPI.post(user3, %{
1442 "visibility" => "private",
1443 "in_reply_to_status_id" => private_activity_2.id
1447 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1448 |> Enum.map(fn a -> a.id end)
1450 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1452 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1454 assert length(activities) == 3
1457 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{"user" => user1})
1458 |> Enum.map(fn a -> a.id end)
1460 assert [public_activity.id, private_activity_1.id] == activities
1461 assert length(activities) == 2
1465 describe "update" do
1466 setup do: clear_config([:instance, :max_pinned_statuses])
1468 test "it creates an update activity with the new user data" do
1469 user = insert(:user)
1470 {:ok, user} = User.ensure_keys_present(user)
1471 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
1474 ActivityPub.update(%{
1475 actor: user_data["id"],
1476 to: [user.follower_address],
1481 assert update.data["actor"] == user.ap_id
1482 assert update.data["to"] == [user.follower_address]
1483 assert embedded_object = update.data["object"]
1484 assert embedded_object["id"] == user_data["id"]
1485 assert embedded_object["type"] == user_data["type"]
1489 test "returned pinned statuses" do
1490 Config.put([:instance, :max_pinned_statuses], 3)
1491 user = insert(:user)
1493 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
1494 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
1495 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
1497 CommonAPI.pin(activity_one.id, user)
1498 user = refresh_record(user)
1500 CommonAPI.pin(activity_two.id, user)
1501 user = refresh_record(user)
1503 CommonAPI.pin(activity_three.id, user)
1504 user = refresh_record(user)
1506 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
1508 assert 3 = length(activities)
1511 describe "flag/1" do
1513 reporter = insert(:user)
1514 target_account = insert(:user)
1516 {:ok, activity} = CommonAPI.post(target_account, %{"status" => content})
1517 context = Utils.generate_context_id()
1519 reporter_ap_id = reporter.ap_id
1520 target_ap_id = target_account.ap_id
1521 activity_ap_id = activity.data["id"]
1523 activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1529 target_account: target_account,
1530 reported_activity: activity,
1532 activity_ap_id: activity_ap_id,
1533 activity_with_object: activity_with_object,
1534 reporter_ap_id: reporter_ap_id,
1535 target_ap_id: target_ap_id
1539 test "it can create a Flag activity",
1543 target_account: target_account,
1544 reported_activity: reported_activity,
1546 activity_ap_id: activity_ap_id,
1547 activity_with_object: activity_with_object,
1548 reporter_ap_id: reporter_ap_id,
1549 target_ap_id: target_ap_id
1551 assert {:ok, activity} =
1555 account: target_account,
1556 statuses: [reported_activity],
1562 "id" => activity_ap_id,
1563 "content" => content,
1564 "published" => activity_with_object.object.data["published"],
1565 "actor" => AccountView.render("show.json", %{user: target_account})
1569 actor: ^reporter_ap_id,
1572 "content" => ^content,
1573 "context" => ^context,
1574 "object" => [^target_ap_id, ^note_obj]
1579 test_with_mock "strips status data from Flag, before federating it",
1583 target_account: target_account,
1584 reported_activity: reported_activity,
1594 account: target_account,
1595 statuses: [reported_activity],
1600 put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]])
1602 assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1606 test "fetch_activities/2 returns activities addressed to a list " do
1607 user = insert(:user)
1608 member = insert(:user)
1609 {:ok, list} = Pleroma.List.create("foo", user)
1610 {:ok, list} = Pleroma.List.follow(list, member)
1613 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1615 activity = Repo.preload(activity, :bookmark)
1616 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1618 assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
1622 File.read!("test/fixtures/avatar_data_uri")
1625 describe "fetch_activities_bounded" do
1626 test "fetches private posts for followed users" do
1627 user = insert(:user)
1630 CommonAPI.post(user, %{
1631 "status" => "thought I looked cute might delete later :3",
1632 "visibility" => "private"
1635 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1636 assert result.id == activity.id
1639 test "fetches only public posts for other users" do
1640 user = insert(:user)
1641 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
1643 {:ok, _private_activity} =
1644 CommonAPI.post(user, %{
1645 "status" => "why is tenshi eating a corndog so cute?",
1646 "visibility" => "private"
1649 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1650 assert result.id == activity.id
1654 describe "fetch_follow_information_for_user" do
1655 test "syncronizes following/followers counters" do
1659 follower_address: "http://localhost:4001/users/fuser2/followers",
1660 following_address: "http://localhost:4001/users/fuser2/following"
1663 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1664 assert info.follower_count == 527
1665 assert info.following_count == 267
1668 test "detects hidden followers" do
1671 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1672 %Tesla.Env{status: 403, body: ""}
1675 apply(HttpRequestMock, :request, [env])
1682 follower_address: "http://localhost:4001/users/masto_closed/followers",
1683 following_address: "http://localhost:4001/users/masto_closed/following"
1686 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1687 assert follow_info.hide_followers == true
1688 assert follow_info.hide_follows == false
1691 test "detects hidden follows" do
1694 "http://localhost:4001/users/masto_closed/following?page=1" ->
1695 %Tesla.Env{status: 403, body: ""}
1698 apply(HttpRequestMock, :request, [env])
1705 follower_address: "http://localhost:4001/users/masto_closed/followers",
1706 following_address: "http://localhost:4001/users/masto_closed/following"
1709 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1710 assert follow_info.hide_followers == false
1711 assert follow_info.hide_follows == true
1714 test "detects hidden follows/followers for friendica" do
1718 follower_address: "http://localhost:8080/followers/fuser3",
1719 following_address: "http://localhost:8080/following/fuser3"
1722 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1723 assert follow_info.hide_followers == true
1724 assert follow_info.follower_count == 296
1725 assert follow_info.following_count == 32
1726 assert follow_info.hide_follows == true
1729 test "doesn't crash when follower and following counters are hidden" do
1732 "http://localhost:4001/users/masto_hidden_counters/following" ->
1734 "@context" => "https://www.w3.org/ns/activitystreams",
1735 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1738 "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1739 %Tesla.Env{status: 403, body: ""}
1741 "http://localhost:4001/users/masto_hidden_counters/followers" ->
1743 "@context" => "https://www.w3.org/ns/activitystreams",
1744 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1747 "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1748 %Tesla.Env{status: 403, body: ""}
1755 follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1756 following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1759 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1761 assert follow_info.hide_followers == true
1762 assert follow_info.follower_count == 0
1763 assert follow_info.hide_follows == true
1764 assert follow_info.following_count == 0
1768 describe "fetch_favourites/3" do
1769 test "returns a favourite activities sorted by adds to favorite" do
1770 user = insert(:user)
1771 other_user = insert(:user)
1772 user1 = insert(:user)
1773 user2 = insert(:user)
1774 {:ok, a1} = CommonAPI.post(user1, %{"status" => "bla"})
1775 {:ok, _a2} = CommonAPI.post(user2, %{"status" => "traps are happy"})
1776 {:ok, a3} = CommonAPI.post(user2, %{"status" => "Trees Are "})
1777 {:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "})
1778 {:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "})
1780 {:ok, _} = CommonAPI.favorite(user, a4.id)
1781 {:ok, _} = CommonAPI.favorite(other_user, a3.id)
1782 {:ok, _} = CommonAPI.favorite(user, a3.id)
1783 {:ok, _} = CommonAPI.favorite(other_user, a5.id)
1784 {:ok, _} = CommonAPI.favorite(user, a5.id)
1785 {:ok, _} = CommonAPI.favorite(other_user, a4.id)
1786 {:ok, _} = CommonAPI.favorite(user, a1.id)
1787 {:ok, _} = CommonAPI.favorite(other_user, a1.id)
1788 result = ActivityPub.fetch_favourites(user)
1790 assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1792 result = ActivityPub.fetch_favourites(user, %{"limit" => 2})
1793 assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1797 describe "Move activity" do
1799 %{ap_id: old_ap_id} = old_user = insert(:user)
1800 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1801 follower = insert(:user)
1802 follower_move_opted_out = insert(:user, allow_following_move: false)
1804 User.follow(follower, old_user)
1805 User.follow(follower_move_opted_out, old_user)
1807 assert User.following?(follower, old_user)
1808 assert User.following?(follower_move_opted_out, old_user)
1810 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1815 "actor" => ^old_ap_id,
1816 "object" => ^old_ap_id,
1817 "target" => ^new_ap_id,
1824 "op" => "move_following",
1825 "origin_id" => old_user.id,
1826 "target_id" => new_user.id
1829 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1831 Pleroma.Workers.BackgroundWorker.perform(params, nil)
1833 refute User.following?(follower, old_user)
1834 assert User.following?(follower, new_user)
1836 assert User.following?(follower_move_opted_out, old_user)
1837 refute User.following?(follower_move_opted_out, new_user)
1839 activity = %Activity{activity | object: nil}
1841 assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1843 assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1846 test "old user must be in the new user's `also_known_as` list" do
1847 old_user = insert(:user)
1848 new_user = insert(:user)
1850 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1851 ActivityPub.move(old_user, new_user)
1855 test "doesn't retrieve replies activities with exclude_replies" do
1856 user = insert(:user)
1858 {:ok, activity} = CommonAPI.post(user, %{"status" => "yeah"})
1861 CommonAPI.post(user, %{"status" => "yeah", "in_reply_to_status_id" => activity.id})
1863 [result] = ActivityPub.fetch_public_activities(%{"exclude_replies" => "true"})
1865 assert result.id == activity.id
1867 assert length(ActivityPub.fetch_public_activities()) == 2
1870 describe "replies filtering with public messages" do
1871 setup :public_messages
1873 test "public timeline", %{users: %{u1: user}} do
1876 |> Map.put("type", ["Create", "Announce"])
1877 |> Map.put("local_only", false)
1878 |> Map.put("blocking_user", user)
1879 |> Map.put("muting_user", user)
1880 |> Map.put("reply_filtering_user", user)
1881 |> ActivityPub.fetch_public_activities()
1882 |> Enum.map(& &1.id)
1884 assert length(activities_ids) == 16
1887 test "public timeline with reply_visibility `following`", %{
1893 activities: activities
1897 |> Map.put("type", ["Create", "Announce"])
1898 |> Map.put("local_only", false)
1899 |> Map.put("blocking_user", user)
1900 |> Map.put("muting_user", user)
1901 |> Map.put("reply_visibility", "following")
1902 |> Map.put("reply_filtering_user", user)
1903 |> ActivityPub.fetch_public_activities()
1904 |> Enum.map(& &1.id)
1906 assert length(activities_ids) == 14
1909 Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
1911 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1914 test "public timeline with reply_visibility `self`", %{
1920 activities: activities
1924 |> Map.put("type", ["Create", "Announce"])
1925 |> Map.put("local_only", false)
1926 |> Map.put("blocking_user", user)
1927 |> Map.put("muting_user", user)
1928 |> Map.put("reply_visibility", "self")
1929 |> Map.put("reply_filtering_user", user)
1930 |> ActivityPub.fetch_public_activities()
1931 |> Enum.map(& &1.id)
1933 assert length(activities_ids) == 10
1934 visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
1935 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1938 test "home timeline", %{
1940 activities: activities,
1948 |> Map.put("type", ["Create", "Announce"])
1949 |> Map.put("blocking_user", user)
1950 |> Map.put("muting_user", user)
1951 |> Map.put("user", user)
1952 |> Map.put("reply_filtering_user", user)
1955 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1956 |> Enum.map(& &1.id)
1958 assert length(activities_ids) == 13
1973 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1976 test "home timeline with reply_visibility `following`", %{
1978 activities: activities,
1986 |> Map.put("type", ["Create", "Announce"])
1987 |> Map.put("blocking_user", user)
1988 |> Map.put("muting_user", user)
1989 |> Map.put("user", user)
1990 |> Map.put("reply_visibility", "following")
1991 |> Map.put("reply_filtering_user", user)
1994 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1995 |> Enum.map(& &1.id)
1997 assert length(activities_ids) == 11
2012 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2015 test "home timeline with reply_visibility `self`", %{
2017 activities: activities,
2025 |> Map.put("type", ["Create", "Announce"])
2026 |> Map.put("blocking_user", user)
2027 |> Map.put("muting_user", user)
2028 |> Map.put("user", user)
2029 |> Map.put("reply_visibility", "self")
2030 |> Map.put("reply_filtering_user", user)
2033 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2034 |> Enum.map(& &1.id)
2036 assert length(activities_ids) == 9
2049 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2053 describe "replies filtering with private messages" do
2054 setup :private_messages
2056 test "public timeline", %{users: %{u1: user}} do
2059 |> Map.put("type", ["Create", "Announce"])
2060 |> Map.put("local_only", false)
2061 |> Map.put("blocking_user", user)
2062 |> Map.put("muting_user", user)
2063 |> Map.put("user", user)
2064 |> ActivityPub.fetch_public_activities()
2065 |> Enum.map(& &1.id)
2067 assert activities_ids == []
2070 test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2073 |> Map.put("type", ["Create", "Announce"])
2074 |> Map.put("local_only", false)
2075 |> Map.put("blocking_user", user)
2076 |> Map.put("muting_user", user)
2077 |> Map.put("reply_visibility", "following")
2078 |> Map.put("reply_filtering_user", user)
2079 |> Map.put("user", user)
2080 |> ActivityPub.fetch_public_activities()
2081 |> Enum.map(& &1.id)
2083 assert activities_ids == []
2086 test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
2089 |> Map.put("type", ["Create", "Announce"])
2090 |> Map.put("local_only", false)
2091 |> Map.put("blocking_user", user)
2092 |> Map.put("muting_user", user)
2093 |> Map.put("reply_visibility", "self")
2094 |> Map.put("reply_filtering_user", user)
2095 |> Map.put("user", user)
2096 |> ActivityPub.fetch_public_activities()
2097 |> Enum.map(& &1.id)
2099 assert activities_ids == []
2102 test "home timeline", %{users: %{u1: user}} do
2105 |> Map.put("type", ["Create", "Announce"])
2106 |> Map.put("blocking_user", user)
2107 |> Map.put("muting_user", user)
2108 |> Map.put("user", user)
2111 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2112 |> Enum.map(& &1.id)
2114 assert length(activities_ids) == 12
2117 test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2120 |> Map.put("type", ["Create", "Announce"])
2121 |> Map.put("blocking_user", user)
2122 |> Map.put("muting_user", user)
2123 |> Map.put("user", user)
2124 |> Map.put("reply_visibility", "following")
2125 |> Map.put("reply_filtering_user", user)
2128 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2129 |> Enum.map(& &1.id)
2131 assert length(activities_ids) == 12
2134 test "home timeline with default reply_visibility `self`", %{
2136 activities: activities,
2144 |> Map.put("type", ["Create", "Announce"])
2145 |> Map.put("blocking_user", user)
2146 |> Map.put("muting_user", user)
2147 |> Map.put("user", user)
2148 |> Map.put("reply_visibility", "self")
2149 |> Map.put("reply_filtering_user", user)
2152 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2153 |> Enum.map(& &1.id)
2155 assert length(activities_ids) == 10
2158 Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
2160 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2164 defp public_messages(_) do
2165 [u1, u2, u3, u4] = insert_list(4, :user)
2166 {:ok, u1} = User.follow(u1, u2)
2167 {:ok, u2} = User.follow(u2, u1)
2168 {:ok, u1} = User.follow(u1, u4)
2169 {:ok, u4} = User.follow(u4, u1)
2171 {:ok, u2} = User.follow(u2, u3)
2172 {:ok, u3} = User.follow(u3, u2)
2174 {:ok, a1} = CommonAPI.post(u1, %{"status" => "Status"})
2177 CommonAPI.post(u2, %{
2178 "status" => "@#{u1.nickname} reply from u2 to u1",
2179 "in_reply_to_status_id" => a1.id
2183 CommonAPI.post(u3, %{
2184 "status" => "@#{u1.nickname} reply from u3 to u1",
2185 "in_reply_to_status_id" => a1.id
2189 CommonAPI.post(u4, %{
2190 "status" => "@#{u1.nickname} reply from u4 to u1",
2191 "in_reply_to_status_id" => a1.id
2194 {:ok, a2} = CommonAPI.post(u2, %{"status" => "Status"})
2197 CommonAPI.post(u1, %{
2198 "status" => "@#{u2.nickname} reply from u1 to u2",
2199 "in_reply_to_status_id" => a2.id
2203 CommonAPI.post(u3, %{
2204 "status" => "@#{u2.nickname} reply from u3 to u2",
2205 "in_reply_to_status_id" => a2.id
2209 CommonAPI.post(u4, %{
2210 "status" => "@#{u2.nickname} reply from u4 to u2",
2211 "in_reply_to_status_id" => a2.id
2214 {:ok, a3} = CommonAPI.post(u3, %{"status" => "Status"})
2217 CommonAPI.post(u1, %{
2218 "status" => "@#{u3.nickname} reply from u1 to u3",
2219 "in_reply_to_status_id" => a3.id
2223 CommonAPI.post(u2, %{
2224 "status" => "@#{u3.nickname} reply from u2 to u3",
2225 "in_reply_to_status_id" => a3.id
2229 CommonAPI.post(u4, %{
2230 "status" => "@#{u3.nickname} reply from u4 to u3",
2231 "in_reply_to_status_id" => a3.id
2234 {:ok, a4} = CommonAPI.post(u4, %{"status" => "Status"})
2237 CommonAPI.post(u1, %{
2238 "status" => "@#{u4.nickname} reply from u1 to u4",
2239 "in_reply_to_status_id" => a4.id
2243 CommonAPI.post(u2, %{
2244 "status" => "@#{u4.nickname} reply from u2 to u4",
2245 "in_reply_to_status_id" => a4.id
2249 CommonAPI.post(u3, %{
2250 "status" => "@#{u4.nickname} reply from u3 to u4",
2251 "in_reply_to_status_id" => a4.id
2255 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2256 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2257 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2258 u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
2259 u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
2260 u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
2263 defp private_messages(_) do
2264 [u1, u2, u3, u4] = insert_list(4, :user)
2265 {:ok, u1} = User.follow(u1, u2)
2266 {:ok, u2} = User.follow(u2, u1)
2267 {:ok, u1} = User.follow(u1, u3)
2268 {:ok, u3} = User.follow(u3, u1)
2269 {:ok, u1} = User.follow(u1, u4)
2270 {:ok, u4} = User.follow(u4, u1)
2272 {:ok, u2} = User.follow(u2, u3)
2273 {:ok, u3} = User.follow(u3, u2)
2275 {:ok, a1} = CommonAPI.post(u1, %{"status" => "Status", "visibility" => "private"})
2278 CommonAPI.post(u2, %{
2279 "status" => "@#{u1.nickname} reply from u2 to u1",
2280 "in_reply_to_status_id" => a1.id,
2281 "visibility" => "private"
2285 CommonAPI.post(u3, %{
2286 "status" => "@#{u1.nickname} reply from u3 to u1",
2287 "in_reply_to_status_id" => a1.id,
2288 "visibility" => "private"
2292 CommonAPI.post(u4, %{
2293 "status" => "@#{u1.nickname} reply from u4 to u1",
2294 "in_reply_to_status_id" => a1.id,
2295 "visibility" => "private"
2298 {:ok, a2} = CommonAPI.post(u2, %{"status" => "Status", "visibility" => "private"})
2301 CommonAPI.post(u1, %{
2302 "status" => "@#{u2.nickname} reply from u1 to u2",
2303 "in_reply_to_status_id" => a2.id,
2304 "visibility" => "private"
2308 CommonAPI.post(u3, %{
2309 "status" => "@#{u2.nickname} reply from u3 to u2",
2310 "in_reply_to_status_id" => a2.id,
2311 "visibility" => "private"
2314 {:ok, a3} = CommonAPI.post(u3, %{"status" => "Status", "visibility" => "private"})
2317 CommonAPI.post(u1, %{
2318 "status" => "@#{u3.nickname} reply from u1 to u3",
2319 "in_reply_to_status_id" => a3.id,
2320 "visibility" => "private"
2324 CommonAPI.post(u2, %{
2325 "status" => "@#{u3.nickname} reply from u2 to u3",
2326 "in_reply_to_status_id" => a3.id,
2327 "visibility" => "private"
2330 {:ok, a4} = CommonAPI.post(u4, %{"status" => "Status", "visibility" => "private"})
2333 CommonAPI.post(u1, %{
2334 "status" => "@#{u4.nickname} reply from u1 to u4",
2335 "in_reply_to_status_id" => a4.id,
2336 "visibility" => "private"
2340 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2341 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2342 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2343 u2: %{r1: r2_1.id, r2: r2_2.id},
2344 u3: %{r1: r3_1.id, r2: r3_2.id},
2348 describe "maybe_update_follow_information/1" do
2350 clear_config([:instance, :external_user_synchronization], true)
2354 ap_id: "https://gensokyo.2hu/users/raymoo",
2355 following_address: "https://gensokyo.2hu/users/following",
2356 follower_address: "https://gensokyo.2hu/users/followers",
2363 test "logs an error when it can't fetch the info", %{user: user} do
2364 assert capture_log(fn ->
2365 ActivityPub.maybe_update_follow_information(user)
2366 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2369 test "just returns the input if the user type is Application", %{
2374 |> Map.put(:type, "Application")
2376 refute capture_log(fn ->
2377 assert ^user = ActivityPub.maybe_update_follow_information(user)
2378 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2381 test "it just returns the input if the user has no following/follower addresses", %{
2386 |> Map.put(:following_address, nil)
2387 |> Map.put(:follower_address, nil)
2389 refute capture_log(fn ->
2390 assert ^user = ActivityPub.maybe_update_follow_information(user)
2391 end) =~ "Follower/Following counter update for #{user.ap_id} failed"