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 "announcing an object" do
943 test "adds an announce activity to the db" do
944 note_activity = insert(:note_activity)
945 object = Object.normalize(note_activity)
948 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
949 assert object.data["announcement_count"] == 1
950 assert object.data["announcements"] == [user.ap_id]
952 assert announce_activity.data["to"] == [
953 User.ap_followers(user),
954 note_activity.data["actor"]
957 assert announce_activity.data["object"] == object.data["id"]
958 assert announce_activity.data["actor"] == user.ap_id
959 assert announce_activity.data["context"] == object.data["context"]
962 test "reverts annouce from object on error" do
963 note_activity = insert(:note_activity)
964 object = Object.normalize(note_activity)
967 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
968 assert {:error, :reverted} = ActivityPub.announce(user, object)
971 reloaded_object = Object.get_by_ap_id(object.data["id"])
972 assert reloaded_object == object
973 refute reloaded_object.data["announcement_count"]
974 refute reloaded_object.data["announcements"]
978 describe "announcing a private object" do
979 test "adds an announce activity to the db if the audience is not widened" do
981 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
982 object = Object.normalize(note_activity)
984 {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false)
986 assert announce_activity.data["to"] == [User.ap_followers(user)]
988 assert announce_activity.data["object"] == object.data["id"]
989 assert announce_activity.data["actor"] == user.ap_id
990 assert announce_activity.data["context"] == object.data["context"]
993 test "does not add an announce activity to the db if the audience is widened" do
995 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
996 object = Object.normalize(note_activity)
998 assert {:error, _} = ActivityPub.announce(user, object, nil, true, true)
1001 test "does not add an announce activity to the db if the announcer is not the author" do
1002 user = insert(:user)
1003 announcer = insert(:user)
1004 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1005 object = Object.normalize(note_activity)
1007 assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false)
1011 describe "unannouncing an object" do
1012 test "unannouncing a previously announced object" do
1013 note_activity = insert(:note_activity)
1014 object = Object.normalize(note_activity)
1015 user = insert(:user)
1017 # Unannouncing an object that is not announced does nothing
1018 {:ok, object} = ActivityPub.unannounce(user, object)
1019 refute object.data["announcement_count"]
1021 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
1022 assert object.data["announcement_count"] == 1
1024 {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
1025 assert object.data["announcement_count"] == 0
1027 assert unannounce_activity.data["to"] == [
1028 User.ap_followers(user),
1029 object.data["actor"]
1032 assert unannounce_activity.data["type"] == "Undo"
1033 assert unannounce_activity.data["object"] == announce_activity.data
1034 assert unannounce_activity.data["actor"] == user.ap_id
1035 assert unannounce_activity.data["context"] == announce_activity.data["context"]
1037 assert Activity.get_by_id(announce_activity.id) == nil
1040 test "reverts unannouncing on error" do
1041 note_activity = insert(:note_activity)
1042 object = Object.normalize(note_activity)
1043 user = insert(:user)
1045 {:ok, _announce_activity, object} = ActivityPub.announce(user, object)
1046 assert object.data["announcement_count"] == 1
1048 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1049 assert {:error, :reverted} = ActivityPub.unannounce(user, object)
1052 object = Object.get_by_ap_id(object.data["id"])
1053 assert object.data["announcement_count"] == 1
1057 describe "uploading files" do
1058 test "copies the file to the configured folder" do
1059 file = %Plug.Upload{
1060 content_type: "image/jpg",
1061 path: Path.absname("test/fixtures/image.jpg"),
1062 filename: "an_image.jpg"
1065 {:ok, %Object{} = object} = ActivityPub.upload(file)
1066 assert object.data["name"] == "an_image.jpg"
1069 test "works with base64 encoded images" do
1074 {:ok, %Object{}} = ActivityPub.upload(file)
1078 describe "fetch the latest Follow" do
1079 test "fetches the latest Follow activity" do
1080 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1081 follower = Repo.get_by(User, ap_id: activity.data["actor"])
1082 followed = Repo.get_by(User, ap_id: activity.data["object"])
1084 assert activity == Utils.fetch_latest_follow(follower, followed)
1088 describe "following / unfollowing" do
1089 test "it reverts follow activity" do
1090 follower = insert(:user)
1091 followed = insert(:user)
1093 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1094 assert {:error, :reverted} = ActivityPub.follow(follower, followed)
1097 assert Repo.aggregate(Activity, :count, :id) == 0
1098 assert Repo.aggregate(Object, :count, :id) == 0
1101 test "it reverts unfollow activity" do
1102 follower = insert(:user)
1103 followed = insert(:user)
1105 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1107 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1108 assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1111 activity = Activity.get_by_id(follow_activity.id)
1112 assert activity.data["type"] == "Follow"
1113 assert activity.data["actor"] == follower.ap_id
1115 assert activity.data["object"] == followed.ap_id
1118 test "creates a follow activity" do
1119 follower = insert(:user)
1120 followed = insert(:user)
1122 {:ok, activity} = ActivityPub.follow(follower, followed)
1123 assert activity.data["type"] == "Follow"
1124 assert activity.data["actor"] == follower.ap_id
1125 assert activity.data["object"] == followed.ap_id
1128 test "creates an undo activity for the last follow" do
1129 follower = insert(:user)
1130 followed = insert(:user)
1132 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1133 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1135 assert activity.data["type"] == "Undo"
1136 assert activity.data["actor"] == follower.ap_id
1138 embedded_object = activity.data["object"]
1139 assert is_map(embedded_object)
1140 assert embedded_object["type"] == "Follow"
1141 assert embedded_object["object"] == followed.ap_id
1142 assert embedded_object["id"] == follow_activity.data["id"]
1145 test "creates an undo activity for a pending follow request" do
1146 follower = insert(:user)
1147 followed = insert(:user, %{locked: true})
1149 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1150 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1152 assert activity.data["type"] == "Undo"
1153 assert activity.data["actor"] == follower.ap_id
1155 embedded_object = activity.data["object"]
1156 assert is_map(embedded_object)
1157 assert embedded_object["type"] == "Follow"
1158 assert embedded_object["object"] == followed.ap_id
1159 assert embedded_object["id"] == follow_activity.data["id"]
1163 describe "blocking / unblocking" do
1164 test "reverts block activity on error" do
1165 [blocker, blocked] = insert_list(2, :user)
1167 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1168 assert {:error, :reverted} = ActivityPub.block(blocker, blocked)
1171 assert Repo.aggregate(Activity, :count, :id) == 0
1172 assert Repo.aggregate(Object, :count, :id) == 0
1175 test "creates a block activity" do
1176 blocker = insert(:user)
1177 blocked = insert(:user)
1179 {:ok, activity} = ActivityPub.block(blocker, blocked)
1181 assert activity.data["type"] == "Block"
1182 assert activity.data["actor"] == blocker.ap_id
1183 assert activity.data["object"] == blocked.ap_id
1186 test "reverts unblock activity on error" do
1187 [blocker, blocked] = insert_list(2, :user)
1188 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1190 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1191 assert {:error, :reverted} = ActivityPub.unblock(blocker, blocked)
1194 assert block_activity.data["type"] == "Block"
1195 assert block_activity.data["actor"] == blocker.ap_id
1197 assert Repo.aggregate(Activity, :count, :id) == 1
1198 assert Repo.aggregate(Object, :count, :id) == 1
1201 test "creates an undo activity for the last block" do
1202 blocker = insert(:user)
1203 blocked = insert(:user)
1205 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1206 {:ok, activity} = ActivityPub.unblock(blocker, blocked)
1208 assert activity.data["type"] == "Undo"
1209 assert activity.data["actor"] == blocker.ap_id
1211 embedded_object = activity.data["object"]
1212 assert is_map(embedded_object)
1213 assert embedded_object["type"] == "Block"
1214 assert embedded_object["object"] == blocked.ap_id
1215 assert embedded_object["id"] == block_activity.data["id"]
1219 describe "deletion" do
1220 setup do: clear_config([:instance, :rewrite_policy])
1222 test "it reverts deletion on error" do
1223 note = insert(:note_activity)
1224 object = Object.normalize(note)
1226 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1227 assert {:error, :reverted} = ActivityPub.delete(object)
1230 assert Repo.aggregate(Activity, :count, :id) == 1
1231 assert Repo.get(Object, object.id) == object
1232 assert Activity.get_by_id(note.id) == note
1235 test "it creates a delete activity and deletes the original object" do
1236 note = insert(:note_activity)
1237 object = Object.normalize(note)
1238 {:ok, delete} = ActivityPub.delete(object)
1240 assert delete.data["type"] == "Delete"
1241 assert delete.data["actor"] == note.data["actor"]
1242 assert delete.data["object"] == object.data["id"]
1244 assert Activity.get_by_id(delete.id) != nil
1246 assert Repo.get(Object, object.id).data["type"] == "Tombstone"
1249 test "it doesn't fail when an activity was already deleted" do
1250 {:ok, delete} = insert(:note_activity) |> Object.normalize() |> ActivityPub.delete()
1252 assert {:ok, ^delete} = delete |> Object.normalize() |> ActivityPub.delete()
1255 test "decrements user note count only for public activities" do
1256 user = insert(:user, note_count: 10)
1259 CommonAPI.post(User.get_cached_by_id(user.id), %{
1261 "visibility" => "public"
1265 CommonAPI.post(User.get_cached_by_id(user.id), %{
1267 "visibility" => "unlisted"
1271 CommonAPI.post(User.get_cached_by_id(user.id), %{
1273 "visibility" => "private"
1277 CommonAPI.post(User.get_cached_by_id(user.id), %{
1279 "visibility" => "direct"
1282 {:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
1283 {:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
1284 {:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
1285 {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
1287 user = User.get_cached_by_id(user.id)
1288 assert user.note_count == 10
1291 test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
1292 user = insert(:user)
1293 note = insert(:note_activity)
1294 object = Object.normalize(note)
1300 "actor" => object.data["actor"],
1301 "id" => object.data["id"],
1302 "to" => [user.ap_id],
1306 |> Object.update_and_set_cache()
1308 {:ok, delete} = ActivityPub.delete(object)
1310 assert user.ap_id in delete.data["to"]
1313 test "decreases reply count" do
1314 user = insert(:user)
1315 user2 = insert(:user)
1317 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
1318 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
1319 ap_id = activity.data["id"]
1321 {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
1322 {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
1323 {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
1324 {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
1326 _ = CommonAPI.delete(direct_reply.id, user2)
1327 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1328 assert object.data["repliesCount"] == 2
1330 _ = CommonAPI.delete(private_reply.id, user2)
1331 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1332 assert object.data["repliesCount"] == 2
1334 _ = CommonAPI.delete(public_reply.id, user2)
1335 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1336 assert object.data["repliesCount"] == 1
1338 _ = CommonAPI.delete(unlisted_reply.id, user2)
1339 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1340 assert object.data["repliesCount"] == 0
1343 test "it passes delete activity through MRF before deleting the object" do
1344 Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy)
1346 note = insert(:note_activity)
1347 object = Object.normalize(note)
1349 {:error, {:reject, _}} = ActivityPub.delete(object)
1351 assert Activity.get_by_id(note.id)
1352 assert Repo.get(Object, object.id).data["type"] == object.data["type"]
1356 describe "timeline post-processing" do
1357 test "it filters broken threads" do
1358 user1 = insert(:user)
1359 user2 = insert(:user)
1360 user3 = insert(:user)
1362 {:ok, user1} = User.follow(user1, user3)
1363 assert User.following?(user1, user3)
1365 {:ok, user2} = User.follow(user2, user3)
1366 assert User.following?(user2, user3)
1368 {:ok, user3} = User.follow(user3, user2)
1369 assert User.following?(user3, user2)
1371 {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"})
1373 {:ok, private_activity_1} =
1374 CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
1376 {:ok, private_activity_2} =
1377 CommonAPI.post(user2, %{
1379 "visibility" => "private",
1380 "in_reply_to_status_id" => private_activity_1.id
1383 {:ok, private_activity_3} =
1384 CommonAPI.post(user3, %{
1386 "visibility" => "private",
1387 "in_reply_to_status_id" => private_activity_2.id
1391 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1392 |> Enum.map(fn a -> a.id end)
1394 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1396 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1398 assert length(activities) == 3
1401 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{"user" => user1})
1402 |> Enum.map(fn a -> a.id end)
1404 assert [public_activity.id, private_activity_1.id] == activities
1405 assert length(activities) == 2
1409 describe "update" do
1410 setup do: clear_config([:instance, :max_pinned_statuses])
1412 test "it creates an update activity with the new user data" do
1413 user = insert(:user)
1414 {:ok, user} = User.ensure_keys_present(user)
1415 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
1418 ActivityPub.update(%{
1419 actor: user_data["id"],
1420 to: [user.follower_address],
1425 assert update.data["actor"] == user.ap_id
1426 assert update.data["to"] == [user.follower_address]
1427 assert embedded_object = update.data["object"]
1428 assert embedded_object["id"] == user_data["id"]
1429 assert embedded_object["type"] == user_data["type"]
1433 test "returned pinned statuses" do
1434 Config.put([:instance, :max_pinned_statuses], 3)
1435 user = insert(:user)
1437 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
1438 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
1439 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
1441 CommonAPI.pin(activity_one.id, user)
1442 user = refresh_record(user)
1444 CommonAPI.pin(activity_two.id, user)
1445 user = refresh_record(user)
1447 CommonAPI.pin(activity_three.id, user)
1448 user = refresh_record(user)
1450 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
1452 assert 3 = length(activities)
1455 describe "flag/1" do
1457 reporter = insert(:user)
1458 target_account = insert(:user)
1460 {:ok, activity} = CommonAPI.post(target_account, %{"status" => content})
1461 context = Utils.generate_context_id()
1463 reporter_ap_id = reporter.ap_id
1464 target_ap_id = target_account.ap_id
1465 activity_ap_id = activity.data["id"]
1467 activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1473 target_account: target_account,
1474 reported_activity: activity,
1476 activity_ap_id: activity_ap_id,
1477 activity_with_object: activity_with_object,
1478 reporter_ap_id: reporter_ap_id,
1479 target_ap_id: target_ap_id
1483 test "it can create a Flag activity",
1487 target_account: target_account,
1488 reported_activity: reported_activity,
1490 activity_ap_id: activity_ap_id,
1491 activity_with_object: activity_with_object,
1492 reporter_ap_id: reporter_ap_id,
1493 target_ap_id: target_ap_id
1495 assert {:ok, activity} =
1499 account: target_account,
1500 statuses: [reported_activity],
1506 "id" => activity_ap_id,
1507 "content" => content,
1508 "published" => activity_with_object.object.data["published"],
1509 "actor" => AccountView.render("show.json", %{user: target_account})
1513 actor: ^reporter_ap_id,
1516 "content" => ^content,
1517 "context" => ^context,
1518 "object" => [^target_ap_id, ^note_obj]
1523 test_with_mock "strips status data from Flag, before federating it",
1527 target_account: target_account,
1528 reported_activity: reported_activity,
1538 account: target_account,
1539 statuses: [reported_activity],
1544 put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]])
1546 assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1550 test "fetch_activities/2 returns activities addressed to a list " do
1551 user = insert(:user)
1552 member = insert(:user)
1553 {:ok, list} = Pleroma.List.create("foo", user)
1554 {:ok, list} = Pleroma.List.follow(list, member)
1557 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1559 activity = Repo.preload(activity, :bookmark)
1560 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1562 assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
1566 File.read!("test/fixtures/avatar_data_uri")
1569 describe "fetch_activities_bounded" do
1570 test "fetches private posts for followed users" do
1571 user = insert(:user)
1574 CommonAPI.post(user, %{
1575 "status" => "thought I looked cute might delete later :3",
1576 "visibility" => "private"
1579 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1580 assert result.id == activity.id
1583 test "fetches only public posts for other users" do
1584 user = insert(:user)
1585 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
1587 {:ok, _private_activity} =
1588 CommonAPI.post(user, %{
1589 "status" => "why is tenshi eating a corndog so cute?",
1590 "visibility" => "private"
1593 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1594 assert result.id == activity.id
1598 describe "fetch_follow_information_for_user" do
1599 test "syncronizes following/followers counters" do
1603 follower_address: "http://localhost:4001/users/fuser2/followers",
1604 following_address: "http://localhost:4001/users/fuser2/following"
1607 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1608 assert info.follower_count == 527
1609 assert info.following_count == 267
1612 test "detects hidden followers" do
1615 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1616 %Tesla.Env{status: 403, body: ""}
1619 apply(HttpRequestMock, :request, [env])
1626 follower_address: "http://localhost:4001/users/masto_closed/followers",
1627 following_address: "http://localhost:4001/users/masto_closed/following"
1630 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1631 assert follow_info.hide_followers == true
1632 assert follow_info.hide_follows == false
1635 test "detects hidden follows" do
1638 "http://localhost:4001/users/masto_closed/following?page=1" ->
1639 %Tesla.Env{status: 403, body: ""}
1642 apply(HttpRequestMock, :request, [env])
1649 follower_address: "http://localhost:4001/users/masto_closed/followers",
1650 following_address: "http://localhost:4001/users/masto_closed/following"
1653 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1654 assert follow_info.hide_followers == false
1655 assert follow_info.hide_follows == true
1658 test "detects hidden follows/followers for friendica" do
1662 follower_address: "http://localhost:8080/followers/fuser3",
1663 following_address: "http://localhost:8080/following/fuser3"
1666 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1667 assert follow_info.hide_followers == true
1668 assert follow_info.follower_count == 296
1669 assert follow_info.following_count == 32
1670 assert follow_info.hide_follows == true
1673 test "doesn't crash when follower and following counters are hidden" do
1676 "http://localhost:4001/users/masto_hidden_counters/following" ->
1678 "@context" => "https://www.w3.org/ns/activitystreams",
1679 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1682 "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1683 %Tesla.Env{status: 403, body: ""}
1685 "http://localhost:4001/users/masto_hidden_counters/followers" ->
1687 "@context" => "https://www.w3.org/ns/activitystreams",
1688 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1691 "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1692 %Tesla.Env{status: 403, body: ""}
1699 follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1700 following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1703 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1705 assert follow_info.hide_followers == true
1706 assert follow_info.follower_count == 0
1707 assert follow_info.hide_follows == true
1708 assert follow_info.following_count == 0
1712 describe "fetch_favourites/3" do
1713 test "returns a favourite activities sorted by adds to favorite" do
1714 user = insert(:user)
1715 other_user = insert(:user)
1716 user1 = insert(:user)
1717 user2 = insert(:user)
1718 {:ok, a1} = CommonAPI.post(user1, %{"status" => "bla"})
1719 {:ok, _a2} = CommonAPI.post(user2, %{"status" => "traps are happy"})
1720 {:ok, a3} = CommonAPI.post(user2, %{"status" => "Trees Are "})
1721 {:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "})
1722 {:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "})
1724 {:ok, _} = CommonAPI.favorite(user, a4.id)
1725 {:ok, _} = CommonAPI.favorite(other_user, a3.id)
1726 {:ok, _} = CommonAPI.favorite(user, a3.id)
1727 {:ok, _} = CommonAPI.favorite(other_user, a5.id)
1728 {:ok, _} = CommonAPI.favorite(user, a5.id)
1729 {:ok, _} = CommonAPI.favorite(other_user, a4.id)
1730 {:ok, _} = CommonAPI.favorite(user, a1.id)
1731 {:ok, _} = CommonAPI.favorite(other_user, a1.id)
1732 result = ActivityPub.fetch_favourites(user)
1734 assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1736 result = ActivityPub.fetch_favourites(user, %{"limit" => 2})
1737 assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1741 describe "Move activity" do
1743 %{ap_id: old_ap_id} = old_user = insert(:user)
1744 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1745 follower = insert(:user)
1746 follower_move_opted_out = insert(:user, allow_following_move: false)
1748 User.follow(follower, old_user)
1749 User.follow(follower_move_opted_out, old_user)
1751 assert User.following?(follower, old_user)
1752 assert User.following?(follower_move_opted_out, old_user)
1754 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1759 "actor" => ^old_ap_id,
1760 "object" => ^old_ap_id,
1761 "target" => ^new_ap_id,
1768 "op" => "move_following",
1769 "origin_id" => old_user.id,
1770 "target_id" => new_user.id
1773 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1775 Pleroma.Workers.BackgroundWorker.perform(params, nil)
1777 refute User.following?(follower, old_user)
1778 assert User.following?(follower, new_user)
1780 assert User.following?(follower_move_opted_out, old_user)
1781 refute User.following?(follower_move_opted_out, new_user)
1783 activity = %Activity{activity | object: nil}
1785 assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1787 assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1790 test "old user must be in the new user's `also_known_as` list" do
1791 old_user = insert(:user)
1792 new_user = insert(:user)
1794 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1795 ActivityPub.move(old_user, new_user)
1799 test "doesn't retrieve replies activities with exclude_replies" do
1800 user = insert(:user)
1802 {:ok, activity} = CommonAPI.post(user, %{"status" => "yeah"})
1805 CommonAPI.post(user, %{"status" => "yeah", "in_reply_to_status_id" => activity.id})
1807 [result] = ActivityPub.fetch_public_activities(%{"exclude_replies" => "true"})
1809 assert result.id == activity.id
1811 assert length(ActivityPub.fetch_public_activities()) == 2
1814 describe "replies filtering with public messages" do
1815 setup :public_messages
1817 test "public timeline", %{users: %{u1: user}} do
1820 |> Map.put("type", ["Create", "Announce"])
1821 |> Map.put("local_only", false)
1822 |> Map.put("blocking_user", user)
1823 |> Map.put("muting_user", user)
1824 |> Map.put("reply_filtering_user", user)
1825 |> ActivityPub.fetch_public_activities()
1826 |> Enum.map(& &1.id)
1828 assert length(activities_ids) == 16
1831 test "public timeline with reply_visibility `following`", %{
1837 activities: activities
1841 |> Map.put("type", ["Create", "Announce"])
1842 |> Map.put("local_only", false)
1843 |> Map.put("blocking_user", user)
1844 |> Map.put("muting_user", user)
1845 |> Map.put("reply_visibility", "following")
1846 |> Map.put("reply_filtering_user", user)
1847 |> ActivityPub.fetch_public_activities()
1848 |> Enum.map(& &1.id)
1850 assert length(activities_ids) == 14
1853 Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
1855 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1858 test "public timeline with reply_visibility `self`", %{
1864 activities: activities
1868 |> Map.put("type", ["Create", "Announce"])
1869 |> Map.put("local_only", false)
1870 |> Map.put("blocking_user", user)
1871 |> Map.put("muting_user", user)
1872 |> Map.put("reply_visibility", "self")
1873 |> Map.put("reply_filtering_user", user)
1874 |> ActivityPub.fetch_public_activities()
1875 |> Enum.map(& &1.id)
1877 assert length(activities_ids) == 10
1878 visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
1879 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1882 test "home timeline", %{
1884 activities: activities,
1892 |> Map.put("type", ["Create", "Announce"])
1893 |> Map.put("blocking_user", user)
1894 |> Map.put("muting_user", user)
1895 |> Map.put("user", user)
1896 |> Map.put("reply_filtering_user", user)
1899 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1900 |> Enum.map(& &1.id)
1902 assert length(activities_ids) == 13
1917 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1920 test "home timeline with reply_visibility `following`", %{
1922 activities: activities,
1930 |> Map.put("type", ["Create", "Announce"])
1931 |> Map.put("blocking_user", user)
1932 |> Map.put("muting_user", user)
1933 |> Map.put("user", user)
1934 |> Map.put("reply_visibility", "following")
1935 |> Map.put("reply_filtering_user", user)
1938 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1939 |> Enum.map(& &1.id)
1941 assert length(activities_ids) == 11
1956 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1959 test "home timeline with reply_visibility `self`", %{
1961 activities: activities,
1969 |> Map.put("type", ["Create", "Announce"])
1970 |> Map.put("blocking_user", user)
1971 |> Map.put("muting_user", user)
1972 |> Map.put("user", user)
1973 |> Map.put("reply_visibility", "self")
1974 |> Map.put("reply_filtering_user", user)
1977 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1978 |> Enum.map(& &1.id)
1980 assert length(activities_ids) == 9
1993 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1997 describe "replies filtering with private messages" do
1998 setup :private_messages
2000 test "public timeline", %{users: %{u1: user}} do
2003 |> Map.put("type", ["Create", "Announce"])
2004 |> Map.put("local_only", false)
2005 |> Map.put("blocking_user", user)
2006 |> Map.put("muting_user", user)
2007 |> Map.put("user", user)
2008 |> ActivityPub.fetch_public_activities()
2009 |> Enum.map(& &1.id)
2011 assert activities_ids == []
2014 test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2017 |> Map.put("type", ["Create", "Announce"])
2018 |> Map.put("local_only", false)
2019 |> Map.put("blocking_user", user)
2020 |> Map.put("muting_user", user)
2021 |> Map.put("reply_visibility", "following")
2022 |> Map.put("reply_filtering_user", user)
2023 |> Map.put("user", user)
2024 |> ActivityPub.fetch_public_activities()
2025 |> Enum.map(& &1.id)
2027 assert activities_ids == []
2030 test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
2033 |> Map.put("type", ["Create", "Announce"])
2034 |> Map.put("local_only", false)
2035 |> Map.put("blocking_user", user)
2036 |> Map.put("muting_user", user)
2037 |> Map.put("reply_visibility", "self")
2038 |> Map.put("reply_filtering_user", user)
2039 |> Map.put("user", user)
2040 |> ActivityPub.fetch_public_activities()
2041 |> Enum.map(& &1.id)
2043 assert activities_ids == []
2046 test "home timeline", %{users: %{u1: user}} do
2049 |> Map.put("type", ["Create", "Announce"])
2050 |> Map.put("blocking_user", user)
2051 |> Map.put("muting_user", user)
2052 |> Map.put("user", user)
2055 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2056 |> Enum.map(& &1.id)
2058 assert length(activities_ids) == 12
2061 test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2064 |> Map.put("type", ["Create", "Announce"])
2065 |> Map.put("blocking_user", user)
2066 |> Map.put("muting_user", user)
2067 |> Map.put("user", user)
2068 |> Map.put("reply_visibility", "following")
2069 |> Map.put("reply_filtering_user", user)
2072 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2073 |> Enum.map(& &1.id)
2075 assert length(activities_ids) == 12
2078 test "home timeline with default reply_visibility `self`", %{
2080 activities: activities,
2088 |> Map.put("type", ["Create", "Announce"])
2089 |> Map.put("blocking_user", user)
2090 |> Map.put("muting_user", user)
2091 |> Map.put("user", user)
2092 |> Map.put("reply_visibility", "self")
2093 |> Map.put("reply_filtering_user", user)
2096 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2097 |> Enum.map(& &1.id)
2099 assert length(activities_ids) == 10
2102 Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
2104 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2108 defp public_messages(_) do
2109 [u1, u2, u3, u4] = insert_list(4, :user)
2110 {:ok, u1} = User.follow(u1, u2)
2111 {:ok, u2} = User.follow(u2, u1)
2112 {:ok, u1} = User.follow(u1, u4)
2113 {:ok, u4} = User.follow(u4, u1)
2115 {:ok, u2} = User.follow(u2, u3)
2116 {:ok, u3} = User.follow(u3, u2)
2118 {:ok, a1} = CommonAPI.post(u1, %{"status" => "Status"})
2121 CommonAPI.post(u2, %{
2122 "status" => "@#{u1.nickname} reply from u2 to u1",
2123 "in_reply_to_status_id" => a1.id
2127 CommonAPI.post(u3, %{
2128 "status" => "@#{u1.nickname} reply from u3 to u1",
2129 "in_reply_to_status_id" => a1.id
2133 CommonAPI.post(u4, %{
2134 "status" => "@#{u1.nickname} reply from u4 to u1",
2135 "in_reply_to_status_id" => a1.id
2138 {:ok, a2} = CommonAPI.post(u2, %{"status" => "Status"})
2141 CommonAPI.post(u1, %{
2142 "status" => "@#{u2.nickname} reply from u1 to u2",
2143 "in_reply_to_status_id" => a2.id
2147 CommonAPI.post(u3, %{
2148 "status" => "@#{u2.nickname} reply from u3 to u2",
2149 "in_reply_to_status_id" => a2.id
2153 CommonAPI.post(u4, %{
2154 "status" => "@#{u2.nickname} reply from u4 to u2",
2155 "in_reply_to_status_id" => a2.id
2158 {:ok, a3} = CommonAPI.post(u3, %{"status" => "Status"})
2161 CommonAPI.post(u1, %{
2162 "status" => "@#{u3.nickname} reply from u1 to u3",
2163 "in_reply_to_status_id" => a3.id
2167 CommonAPI.post(u2, %{
2168 "status" => "@#{u3.nickname} reply from u2 to u3",
2169 "in_reply_to_status_id" => a3.id
2173 CommonAPI.post(u4, %{
2174 "status" => "@#{u3.nickname} reply from u4 to u3",
2175 "in_reply_to_status_id" => a3.id
2178 {:ok, a4} = CommonAPI.post(u4, %{"status" => "Status"})
2181 CommonAPI.post(u1, %{
2182 "status" => "@#{u4.nickname} reply from u1 to u4",
2183 "in_reply_to_status_id" => a4.id
2187 CommonAPI.post(u2, %{
2188 "status" => "@#{u4.nickname} reply from u2 to u4",
2189 "in_reply_to_status_id" => a4.id
2193 CommonAPI.post(u3, %{
2194 "status" => "@#{u4.nickname} reply from u3 to u4",
2195 "in_reply_to_status_id" => a4.id
2199 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2200 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2201 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2202 u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
2203 u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
2204 u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
2207 defp private_messages(_) do
2208 [u1, u2, u3, u4] = insert_list(4, :user)
2209 {:ok, u1} = User.follow(u1, u2)
2210 {:ok, u2} = User.follow(u2, u1)
2211 {:ok, u1} = User.follow(u1, u3)
2212 {:ok, u3} = User.follow(u3, u1)
2213 {:ok, u1} = User.follow(u1, u4)
2214 {:ok, u4} = User.follow(u4, u1)
2216 {:ok, u2} = User.follow(u2, u3)
2217 {:ok, u3} = User.follow(u3, u2)
2219 {:ok, a1} = CommonAPI.post(u1, %{"status" => "Status", "visibility" => "private"})
2222 CommonAPI.post(u2, %{
2223 "status" => "@#{u1.nickname} reply from u2 to u1",
2224 "in_reply_to_status_id" => a1.id,
2225 "visibility" => "private"
2229 CommonAPI.post(u3, %{
2230 "status" => "@#{u1.nickname} reply from u3 to u1",
2231 "in_reply_to_status_id" => a1.id,
2232 "visibility" => "private"
2236 CommonAPI.post(u4, %{
2237 "status" => "@#{u1.nickname} reply from u4 to u1",
2238 "in_reply_to_status_id" => a1.id,
2239 "visibility" => "private"
2242 {:ok, a2} = CommonAPI.post(u2, %{"status" => "Status", "visibility" => "private"})
2245 CommonAPI.post(u1, %{
2246 "status" => "@#{u2.nickname} reply from u1 to u2",
2247 "in_reply_to_status_id" => a2.id,
2248 "visibility" => "private"
2252 CommonAPI.post(u3, %{
2253 "status" => "@#{u2.nickname} reply from u3 to u2",
2254 "in_reply_to_status_id" => a2.id,
2255 "visibility" => "private"
2258 {:ok, a3} = CommonAPI.post(u3, %{"status" => "Status", "visibility" => "private"})
2261 CommonAPI.post(u1, %{
2262 "status" => "@#{u3.nickname} reply from u1 to u3",
2263 "in_reply_to_status_id" => a3.id,
2264 "visibility" => "private"
2268 CommonAPI.post(u2, %{
2269 "status" => "@#{u3.nickname} reply from u2 to u3",
2270 "in_reply_to_status_id" => a3.id,
2271 "visibility" => "private"
2274 {:ok, a4} = CommonAPI.post(u4, %{"status" => "Status", "visibility" => "private"})
2277 CommonAPI.post(u1, %{
2278 "status" => "@#{u4.nickname} reply from u1 to u4",
2279 "in_reply_to_status_id" => a4.id,
2280 "visibility" => "private"
2284 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2285 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2286 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2287 u2: %{r1: r2_1.id, r2: r2_2.id},
2288 u3: %{r1: r3_1.id, r2: r3_2.id},
2292 describe "maybe_update_follow_information/1" do
2294 clear_config([:instance, :external_user_synchronization], true)
2298 ap_id: "https://gensokyo.2hu/users/raymoo",
2299 following_address: "https://gensokyo.2hu/users/following",
2300 follower_address: "https://gensokyo.2hu/users/followers",
2307 test "logs an error when it can't fetch the info", %{user: user} do
2308 assert capture_log(fn ->
2309 ActivityPub.maybe_update_follow_information(user)
2310 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2313 test "just returns the input if the user type is Application", %{
2318 |> Map.put(:type, "Application")
2320 refute capture_log(fn ->
2321 assert ^user = ActivityPub.maybe_update_follow_information(user)
2322 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2325 test "it just returns the input if the user has no following/follower addresses", %{
2330 |> Map.put(:following_address, nil)
2331 |> Map.put(:follower_address, nil)
2333 refute capture_log(fn ->
2334 assert ^user = ActivityPub.maybe_update_follow_information(user)
2335 end) =~ "Follower/Following counter update for #{user.ap_id} failed"