a6f8b6152ebc499e91915deda6bfb45468531343
[anni] / test / pleroma / web / activity_pub / activity_pub_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
6   use Pleroma.DataCase
7   use Oban.Testing, repo: Pleroma.Repo
8
9   alias Pleroma.Activity
10   alias Pleroma.Builders.ActivityBuilder
11   alias Pleroma.Config
12   alias Pleroma.Notification
13   alias Pleroma.Object
14   alias Pleroma.User
15   alias Pleroma.Web.ActivityPub.ActivityPub
16   alias Pleroma.Web.ActivityPub.Utils
17   alias Pleroma.Web.AdminAPI.AccountView
18   alias Pleroma.Web.CommonAPI
19
20   import ExUnit.CaptureLog
21   import Mock
22   import Pleroma.Factory
23   import Tesla.Mock
24
25   setup do
26     mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
27     :ok
28   end
29
30   setup do: clear_config([:instance, :federating])
31
32   describe "streaming out participations" do
33     test "it streams them out" do
34       user = insert(:user)
35       {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
36
37       {:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity)
38
39       participations =
40         conversation.participations
41         |> Repo.preload(:user)
42
43       with_mock Pleroma.Web.Streamer,
44         stream: fn _, _ -> nil end do
45         ActivityPub.stream_out_participations(conversation.participations)
46
47         assert called(Pleroma.Web.Streamer.stream("participation", participations))
48       end
49     end
50
51     test "streams them out on activity creation" do
52       user_one = insert(:user)
53       user_two = insert(:user)
54
55       with_mock Pleroma.Web.Streamer,
56         stream: fn _, _ -> nil end do
57         {:ok, activity} =
58           CommonAPI.post(user_one, %{
59             status: "@#{user_two.nickname}",
60             visibility: "direct"
61           })
62
63         conversation =
64           activity.data["context"]
65           |> Pleroma.Conversation.get_for_ap_id()
66           |> Repo.preload(participations: :user)
67
68         assert called(Pleroma.Web.Streamer.stream("participation", conversation.participations))
69       end
70     end
71   end
72
73   describe "fetching restricted by visibility" do
74     test "it restricts by the appropriate visibility" do
75       user = insert(:user)
76
77       {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
78
79       {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
80
81       {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
82
83       {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
84
85       activities = ActivityPub.fetch_activities([], %{visibility: "direct", actor_id: user.ap_id})
86
87       assert activities == [direct_activity]
88
89       activities =
90         ActivityPub.fetch_activities([], %{visibility: "unlisted", actor_id: user.ap_id})
91
92       assert activities == [unlisted_activity]
93
94       activities =
95         ActivityPub.fetch_activities([], %{visibility: "private", actor_id: user.ap_id})
96
97       assert activities == [private_activity]
98
99       activities = ActivityPub.fetch_activities([], %{visibility: "public", actor_id: user.ap_id})
100
101       assert activities == [public_activity]
102
103       activities =
104         ActivityPub.fetch_activities([], %{
105           visibility: ~w[private public],
106           actor_id: user.ap_id
107         })
108
109       assert activities == [public_activity, private_activity]
110     end
111   end
112
113   describe "fetching excluded by visibility" do
114     test "it excludes by the appropriate visibility" do
115       user = insert(:user)
116
117       {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
118
119       {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
120
121       {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
122
123       {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
124
125       activities =
126         ActivityPub.fetch_activities([], %{
127           exclude_visibilities: "direct",
128           actor_id: user.ap_id
129         })
130
131       assert public_activity in activities
132       assert unlisted_activity in activities
133       assert private_activity in activities
134       refute direct_activity in activities
135
136       activities =
137         ActivityPub.fetch_activities([], %{
138           exclude_visibilities: "unlisted",
139           actor_id: user.ap_id
140         })
141
142       assert public_activity in activities
143       refute unlisted_activity in activities
144       assert private_activity in activities
145       assert direct_activity in activities
146
147       activities =
148         ActivityPub.fetch_activities([], %{
149           exclude_visibilities: "private",
150           actor_id: user.ap_id
151         })
152
153       assert public_activity in activities
154       assert unlisted_activity in activities
155       refute private_activity in activities
156       assert direct_activity in activities
157
158       activities =
159         ActivityPub.fetch_activities([], %{
160           exclude_visibilities: "public",
161           actor_id: user.ap_id
162         })
163
164       refute public_activity in activities
165       assert unlisted_activity in activities
166       assert private_activity in activities
167       assert direct_activity in activities
168     end
169   end
170
171   describe "building a user from his ap id" do
172     test "it returns a user" do
173       user_id = "http://mastodon.example.org/users/admin"
174       {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
175       assert user.ap_id == user_id
176       assert user.nickname == "admin@mastodon.example.org"
177       assert user.ap_enabled
178       assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
179     end
180
181     test "it returns a user that is invisible" do
182       user_id = "http://mastodon.example.org/users/relay"
183       {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
184       assert User.invisible?(user)
185     end
186
187     test "it returns a user that accepts chat messages" do
188       user_id = "http://mastodon.example.org/users/admin"
189       {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
190
191       assert user.accepts_chat_messages
192     end
193
194     test "works for guppe actors" do
195       user_id = "https://gup.pe/u/bernie2020"
196
197       Tesla.Mock.mock(fn
198         %{method: :get, url: ^user_id} ->
199           %Tesla.Env{
200             status: 200,
201             body: File.read!("test/fixtures/guppe-actor.json"),
202             headers: [{"content-type", "application/activity+json"}]
203           }
204       end)
205
206       {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
207
208       assert user.name == "Bernie2020 group"
209       assert user.actor_type == "Group"
210     end
211
212     test "works for bridgy actors" do
213       user_id = "https://fed.brid.gy/jk.nipponalba.scot"
214
215       Tesla.Mock.mock(fn
216         %{method: :get, url: ^user_id} ->
217           %Tesla.Env{
218             status: 200,
219             body: File.read!("test/fixtures/bridgy/actor.json"),
220             headers: [{"content-type", "application/activity+json"}]
221           }
222       end)
223
224       {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
225
226       assert user.actor_type == "Person"
227
228       assert user.avatar == %{
229                "type" => "Image",
230                "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
231              }
232
233       assert user.banner == %{
234                "type" => "Image",
235                "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
236              }
237     end
238
239     test "fetches user featured collection" do
240       ap_id = "https://example.com/users/lain"
241
242       featured_url = "https://example.com/users/lain/collections/featured"
243
244       user_data =
245         "test/fixtures/users_mock/user.json"
246         |> File.read!()
247         |> String.replace("{{nickname}}", "lain")
248         |> Jason.decode!()
249         |> Map.put("featured", featured_url)
250         |> Jason.encode!()
251
252       object_id = Ecto.UUID.generate()
253
254       featured_data =
255         "test/fixtures/mastodon/collections/featured.json"
256         |> File.read!()
257         |> String.replace("{{domain}}", "example.com")
258         |> String.replace("{{nickname}}", "lain")
259         |> String.replace("{{object_id}}", object_id)
260
261       object_url = "https://example.com/objects/#{object_id}"
262
263       object_data =
264         "test/fixtures/statuses/note.json"
265         |> File.read!()
266         |> String.replace("{{object_id}}", object_id)
267         |> String.replace("{{nickname}}", "lain")
268
269       Tesla.Mock.mock(fn
270         %{
271           method: :get,
272           url: ^ap_id
273         } ->
274           %Tesla.Env{
275             status: 200,
276             body: user_data,
277             headers: [{"content-type", "application/activity+json"}]
278           }
279
280         %{
281           method: :get,
282           url: ^featured_url
283         } ->
284           %Tesla.Env{
285             status: 200,
286             body: featured_data,
287             headers: [{"content-type", "application/activity+json"}]
288           }
289       end)
290
291       Tesla.Mock.mock_global(fn
292         %{
293           method: :get,
294           url: ^object_url
295         } ->
296           %Tesla.Env{
297             status: 200,
298             body: object_data,
299             headers: [{"content-type", "application/activity+json"}]
300           }
301       end)
302
303       {:ok, user} = ActivityPub.make_user_from_ap_id(ap_id)
304       Process.sleep(50)
305
306       assert user.featured_address == featured_url
307       assert Map.has_key?(user.pinned_objects, object_url)
308
309       in_db = Pleroma.User.get_by_ap_id(ap_id)
310       assert in_db.featured_address == featured_url
311       assert Map.has_key?(user.pinned_objects, object_url)
312
313       assert %{data: %{"id" => ^object_url}} = Object.get_by_ap_id(object_url)
314     end
315
316     test "fetches user featured collection without embedded object" do
317       ap_id = "https://example.com/users/lain"
318
319       featured_url = "https://example.com/users/lain/collections/featured"
320
321       user_data =
322         "test/fixtures/users_mock/user.json"
323         |> File.read!()
324         |> String.replace("{{nickname}}", "lain")
325         |> Jason.decode!()
326         |> Map.put("featured", featured_url)
327         |> Jason.encode!()
328
329       object_id = Ecto.UUID.generate()
330
331       featured_data =
332         "test/fixtures/mastodon/collections/external_featured.json"
333         |> File.read!()
334         |> String.replace("{{domain}}", "example.com")
335         |> String.replace("{{nickname}}", "lain")
336         |> String.replace("{{object_id}}", object_id)
337
338       object_url = "https://example.com/objects/#{object_id}"
339
340       object_data =
341         "test/fixtures/statuses/note.json"
342         |> File.read!()
343         |> String.replace("{{object_id}}", object_id)
344         |> String.replace("{{nickname}}", "lain")
345
346       Tesla.Mock.mock(fn
347         %{
348           method: :get,
349           url: ^ap_id
350         } ->
351           %Tesla.Env{
352             status: 200,
353             body: user_data,
354             headers: [{"content-type", "application/activity+json"}]
355           }
356
357         %{
358           method: :get,
359           url: ^featured_url
360         } ->
361           %Tesla.Env{
362             status: 200,
363             body: featured_data,
364             headers: [{"content-type", "application/activity+json"}]
365           }
366       end)
367
368       Tesla.Mock.mock_global(fn
369         %{
370           method: :get,
371           url: ^object_url
372         } ->
373           %Tesla.Env{
374             status: 200,
375             body: object_data,
376             headers: [{"content-type", "application/activity+json"}]
377           }
378       end)
379
380       {:ok, user} = ActivityPub.make_user_from_ap_id(ap_id)
381       Process.sleep(50)
382
383       assert user.featured_address == featured_url
384       assert Map.has_key?(user.pinned_objects, object_url)
385
386       in_db = Pleroma.User.get_by_ap_id(ap_id)
387       assert in_db.featured_address == featured_url
388       assert Map.has_key?(user.pinned_objects, object_url)
389
390       assert %{data: %{"id" => ^object_url}} = Object.get_by_ap_id(object_url)
391     end
392
393     test "fetches user birthday information from misskey" do
394       user_id = "https://misskey.io/@mkljczk"
395
396       Tesla.Mock.mock(fn
397         %{
398           method: :get,
399           url: ^user_id
400         } ->
401           %Tesla.Env{
402             status: 200,
403             body: File.read!("test/fixtures/birthdays/misskey-user.json"),
404             headers: [{"content-type", "application/activity+json"}]
405           }
406       end)
407
408       {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
409
410       assert user.birthday == ~D[2001-02-12]
411     end
412   end
413
414   test "it fetches the appropriate tag-restricted posts" do
415     user = insert(:user)
416
417     {:ok, status_one} = CommonAPI.post(user, %{status: ". #TEST"})
418     {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
419     {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #Reject"})
420
421     {:ok, status_four} = CommonAPI.post(user, %{status: ". #Any1 #any2"})
422     {:ok, status_five} = CommonAPI.post(user, %{status: ". #Any2 #any1"})
423
424     for hashtag_timeline_strategy <- [:enabled, :disabled] do
425       clear_config([:features, :improved_hashtag_timeline], hashtag_timeline_strategy)
426
427       fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
428
429       fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["TEST", "essais"]})
430
431       fetch_three =
432         ActivityPub.fetch_activities([], %{
433           type: "Create",
434           tag: ["test", "Essais"],
435           tag_reject: ["reject"]
436         })
437
438       fetch_four =
439         ActivityPub.fetch_activities([], %{
440           type: "Create",
441           tag: ["test"],
442           tag_all: ["test", "REJECT"]
443         })
444
445       # Testing that deduplication (if needed) is done on DB (not Ecto) level; :limit is important
446       fetch_five =
447         ActivityPub.fetch_activities([], %{
448           type: "Create",
449           tag: ["ANY1", "any2"],
450           limit: 2
451         })
452
453       fetch_six =
454         ActivityPub.fetch_activities([], %{
455           type: "Create",
456           tag: ["any1", "Any2"],
457           tag_all: [],
458           tag_reject: []
459         })
460
461       # Regression test: passing empty lists as filter options shouldn't affect the results
462       assert fetch_five == fetch_six
463
464       [fetch_one, fetch_two, fetch_three, fetch_four, fetch_five] =
465         Enum.map([fetch_one, fetch_two, fetch_three, fetch_four, fetch_five], fn statuses ->
466           Enum.map(statuses, fn s -> Repo.preload(s, object: :hashtags) end)
467         end)
468
469       assert fetch_one == [status_one, status_three]
470       assert fetch_two == [status_one, status_two, status_three]
471       assert fetch_three == [status_one, status_two]
472       assert fetch_four == [status_three]
473       assert fetch_five == [status_four, status_five]
474     end
475   end
476
477   describe "insertion" do
478     test "drops activities beyond a certain limit" do
479       limit = Config.get([:instance, :remote_limit])
480
481       random_text =
482         :crypto.strong_rand_bytes(limit + 1)
483         |> Base.encode64()
484         |> binary_part(0, limit + 1)
485
486       data = %{
487         "ok" => true,
488         "object" => %{
489           "content" => random_text
490         }
491       }
492
493       assert {:error, :remote_limit} = ActivityPub.insert(data)
494     end
495
496     test "doesn't drop activities with content being null" do
497       user = insert(:user)
498
499       data = %{
500         "actor" => user.ap_id,
501         "to" => [],
502         "object" => %{
503           "actor" => user.ap_id,
504           "to" => [],
505           "type" => "Note",
506           "content" => nil
507         }
508       }
509
510       assert {:ok, _} = ActivityPub.insert(data)
511     end
512
513     test "returns the activity if one with the same id is already in" do
514       activity = insert(:note_activity)
515       {:ok, new_activity} = ActivityPub.insert(activity.data)
516
517       assert activity.id == new_activity.id
518     end
519
520     test "inserts a given map into the activity database, giving it an id if it has none." do
521       user = insert(:user)
522
523       data = %{
524         "actor" => user.ap_id,
525         "to" => [],
526         "object" => %{
527           "actor" => user.ap_id,
528           "to" => [],
529           "type" => "Note",
530           "content" => "hey"
531         }
532       }
533
534       {:ok, %Activity{} = activity} = ActivityPub.insert(data)
535       assert activity.data["ok"] == data["ok"]
536       assert is_binary(activity.data["id"])
537
538       given_id = "bla"
539
540       data = %{
541         "id" => given_id,
542         "actor" => user.ap_id,
543         "to" => [],
544         "context" => "blabla",
545         "object" => %{
546           "actor" => user.ap_id,
547           "to" => [],
548           "type" => "Note",
549           "content" => "hey"
550         }
551       }
552
553       {:ok, %Activity{} = activity} = ActivityPub.insert(data)
554       assert activity.data["ok"] == data["ok"]
555       assert activity.data["id"] == given_id
556       assert activity.data["context"] == "blabla"
557     end
558
559     test "adds a context when none is there" do
560       user = insert(:user)
561
562       data = %{
563         "actor" => user.ap_id,
564         "to" => [],
565         "object" => %{
566           "actor" => user.ap_id,
567           "to" => [],
568           "type" => "Note",
569           "content" => "hey"
570         }
571       }
572
573       {:ok, %Activity{} = activity} = ActivityPub.insert(data)
574       object = Pleroma.Object.normalize(activity, fetch: false)
575
576       assert is_binary(activity.data["context"])
577       assert is_binary(object.data["context"])
578     end
579
580     test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
581       user = insert(:user)
582
583       data = %{
584         "actor" => user.ap_id,
585         "to" => [],
586         "object" => %{
587           "actor" => user.ap_id,
588           "to" => [],
589           "type" => "Note",
590           "content" => "hey"
591         }
592       }
593
594       {:ok, %Activity{} = activity} = ActivityPub.insert(data)
595       assert object = Object.normalize(activity, fetch: false)
596       assert is_binary(object.data["id"])
597     end
598   end
599
600   describe "listen activities" do
601     test "does not increase user note count" do
602       user = insert(:user)
603
604       {:ok, activity} =
605         ActivityPub.listen(%{
606           to: ["https://www.w3.org/ns/activitystreams#Public"],
607           actor: user,
608           context: "",
609           object: %{
610             "actor" => user.ap_id,
611             "to" => ["https://www.w3.org/ns/activitystreams#Public"],
612             "artist" => "lain",
613             "title" => "lain radio episode 1",
614             "length" => 180_000,
615             "type" => "Audio"
616           }
617         })
618
619       assert activity.actor == user.ap_id
620
621       user = User.get_cached_by_id(user.id)
622       assert user.note_count == 0
623     end
624
625     test "can be fetched into a timeline" do
626       _listen_activity_1 = insert(:listen)
627       _listen_activity_2 = insert(:listen)
628       _listen_activity_3 = insert(:listen)
629
630       timeline = ActivityPub.fetch_activities([], %{type: ["Listen"]})
631
632       assert length(timeline) == 3
633     end
634   end
635
636   describe "create activities" do
637     setup do
638       [user: insert(:user)]
639     end
640
641     test "it reverts create", %{user: user} do
642       with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
643         assert {:error, :reverted} =
644                  ActivityPub.create(%{
645                    to: ["user1", "user2"],
646                    actor: user,
647                    context: "",
648                    object: %{
649                      "to" => ["user1", "user2"],
650                      "type" => "Note",
651                      "content" => "testing"
652                    }
653                  })
654       end
655
656       assert Repo.aggregate(Activity, :count, :id) == 0
657       assert Repo.aggregate(Object, :count, :id) == 0
658     end
659
660     test "creates activity if expiration is not configured and expires_at is not passed", %{
661       user: user
662     } do
663       clear_config([Pleroma.Workers.PurgeExpiredActivity, :enabled], false)
664
665       assert {:ok, _} =
666                ActivityPub.create(%{
667                  to: ["user1", "user2"],
668                  actor: user,
669                  context: "",
670                  object: %{
671                    "to" => ["user1", "user2"],
672                    "type" => "Note",
673                    "content" => "testing"
674                  }
675                })
676     end
677
678     test "rejects activity if expires_at present but expiration is not configured", %{user: user} do
679       clear_config([Pleroma.Workers.PurgeExpiredActivity, :enabled], false)
680
681       assert {:error, :expired_activities_disabled} =
682                ActivityPub.create(%{
683                  to: ["user1", "user2"],
684                  actor: user,
685                  context: "",
686                  object: %{
687                    "to" => ["user1", "user2"],
688                    "type" => "Note",
689                    "content" => "testing"
690                  },
691                  additional: %{
692                    "expires_at" => DateTime.utc_now()
693                  }
694                })
695
696       assert Repo.aggregate(Activity, :count, :id) == 0
697       assert Repo.aggregate(Object, :count, :id) == 0
698     end
699
700     test "removes doubled 'to' recipients", %{user: user} do
701       {:ok, activity} =
702         ActivityPub.create(%{
703           to: ["user1", "user1", "user2"],
704           actor: user,
705           context: "",
706           object: %{
707             "to" => ["user1", "user1", "user2"],
708             "type" => "Note",
709             "content" => "testing"
710           }
711         })
712
713       assert activity.data["to"] == ["user1", "user2"]
714       assert activity.actor == user.ap_id
715       assert activity.recipients == ["user1", "user2", user.ap_id]
716     end
717
718     test "increases user note count only for public activities", %{user: user} do
719       {:ok, _} =
720         CommonAPI.post(User.get_cached_by_id(user.id), %{
721           status: "1",
722           visibility: "public"
723         })
724
725       {:ok, _} =
726         CommonAPI.post(User.get_cached_by_id(user.id), %{
727           status: "2",
728           visibility: "unlisted"
729         })
730
731       {:ok, _} =
732         CommonAPI.post(User.get_cached_by_id(user.id), %{
733           status: "2",
734           visibility: "private"
735         })
736
737       {:ok, _} =
738         CommonAPI.post(User.get_cached_by_id(user.id), %{
739           status: "3",
740           visibility: "direct"
741         })
742
743       user = User.get_cached_by_id(user.id)
744       assert user.note_count == 2
745     end
746
747     test "increases replies count", %{user: user} do
748       user2 = insert(:user)
749
750       {:ok, activity} = CommonAPI.post(user, %{status: "1", visibility: "public"})
751       ap_id = activity.data["id"]
752       reply_data = %{status: "1", in_reply_to_status_id: activity.id}
753
754       # public
755       {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "public"))
756       assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
757       assert object.data["repliesCount"] == 1
758
759       # unlisted
760       {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "unlisted"))
761       assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
762       assert object.data["repliesCount"] == 2
763
764       # private
765       {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "private"))
766       assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
767       assert object.data["repliesCount"] == 2
768
769       # direct
770       {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "direct"))
771       assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
772       assert object.data["repliesCount"] == 2
773     end
774   end
775
776   describe "fetch activities for recipients" do
777     test "retrieve the activities for certain recipients" do
778       {:ok, activity_one} = ActivityBuilder.insert(%{"to" => ["someone"]})
779       {:ok, activity_two} = ActivityBuilder.insert(%{"to" => ["someone_else"]})
780       {:ok, _activity_three} = ActivityBuilder.insert(%{"to" => ["noone"]})
781
782       activities = ActivityPub.fetch_activities(["someone", "someone_else"])
783       assert length(activities) == 2
784       assert activities == [activity_one, activity_two]
785     end
786   end
787
788   describe "fetch activities in context" do
789     test "retrieves activities that have a given context" do
790       {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
791       {:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
792       {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
793       {:ok, _activity_four} = ActivityBuilder.insert(%{"type" => "Announce", "context" => "2hu"})
794       activity_five = insert(:note_activity)
795       user = insert(:user)
796
797       {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})
798
799       activities = ActivityPub.fetch_activities_for_context("2hu", %{blocking_user: user})
800       assert activities == [activity_two, activity]
801     end
802
803     test "doesn't return activities with filtered words" do
804       user = insert(:user)
805       user_two = insert(:user)
806       insert(:filter, user: user, phrase: "test", hide: true)
807
808       {:ok, %{id: id1, data: %{"context" => context}}} = CommonAPI.post(user, %{status: "1"})
809
810       {:ok, %{id: id2}} = CommonAPI.post(user_two, %{status: "2", in_reply_to_status_id: id1})
811
812       {:ok, %{id: id3} = user_activity} =
813         CommonAPI.post(user, %{status: "3 test?", in_reply_to_status_id: id2})
814
815       {:ok, %{id: id4} = filtered_activity} =
816         CommonAPI.post(user_two, %{status: "4 test!", in_reply_to_status_id: id3})
817
818       {:ok, _} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
819
820       activities =
821         context
822         |> ActivityPub.fetch_activities_for_context(%{user: user})
823         |> Enum.map(& &1.id)
824
825       assert length(activities) == 4
826       assert user_activity.id in activities
827       refute filtered_activity.id in activities
828     end
829   end
830
831   test "doesn't return blocked activities" do
832     activity_one = insert(:note_activity)
833     activity_two = insert(:note_activity)
834     activity_three = insert(:note_activity)
835     user = insert(:user)
836     booster = insert(:user)
837     {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
838
839     activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
840
841     assert Enum.member?(activities, activity_two)
842     assert Enum.member?(activities, activity_three)
843     refute Enum.member?(activities, activity_one)
844
845     {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
846
847     activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
848
849     assert Enum.member?(activities, activity_two)
850     assert Enum.member?(activities, activity_three)
851     assert Enum.member?(activities, activity_one)
852
853     {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})
854     {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
855     %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
856     activity_three = Activity.get_by_id(activity_three.id)
857
858     activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
859
860     assert Enum.member?(activities, activity_two)
861     refute Enum.member?(activities, activity_three)
862     refute Enum.member?(activities, boost_activity)
863     assert Enum.member?(activities, activity_one)
864
865     activities = ActivityPub.fetch_activities([], %{blocking_user: nil, skip_preload: true})
866
867     assert Enum.member?(activities, activity_two)
868     assert Enum.member?(activities, activity_three)
869     assert Enum.member?(activities, boost_activity)
870     assert Enum.member?(activities, activity_one)
871   end
872
873   test "doesn't return activities from deactivated users" do
874     _user = insert(:user)
875     deactivated = insert(:user)
876     active = insert(:user)
877     {:ok, activity_one} = CommonAPI.post(deactivated, %{status: "hey!"})
878     {:ok, activity_two} = CommonAPI.post(active, %{status: "yay!"})
879     {:ok, _updated_user} = User.set_activation(deactivated, false)
880
881     activities = ActivityPub.fetch_activities([], %{})
882
883     refute Enum.member?(activities, activity_one)
884     assert Enum.member?(activities, activity_two)
885   end
886
887   test "always see your own posts even when they address people you block" do
888     user = insert(:user)
889     blockee = insert(:user)
890
891     {:ok, _} = User.block(user, blockee)
892     {:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{blockee.nickname}"})
893
894     activities = ActivityPub.fetch_activities([], %{blocking_user: user})
895
896     assert Enum.member?(activities, activity)
897   end
898
899   test "doesn't return transitive interactions concerning blocked users" do
900     blocker = insert(:user)
901     blockee = insert(:user)
902     friend = insert(:user)
903
904     {:ok, _user_relationship} = User.block(blocker, blockee)
905
906     {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
907
908     {:ok, activity_two} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
909
910     {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
911
912     {:ok, activity_four} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
913
914     activities = ActivityPub.fetch_activities([], %{blocking_user: blocker})
915
916     assert Enum.member?(activities, activity_one)
917     refute Enum.member?(activities, activity_two)
918     refute Enum.member?(activities, activity_three)
919     refute Enum.member?(activities, activity_four)
920   end
921
922   test "doesn't return announce activities with blocked users in 'to'" do
923     blocker = insert(:user)
924     blockee = insert(:user)
925     friend = insert(:user)
926
927     {:ok, _user_relationship} = User.block(blocker, blockee)
928
929     {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
930
931     {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
932
933     {:ok, activity_three} = CommonAPI.repeat(activity_two.id, friend)
934
935     activities =
936       ActivityPub.fetch_activities([], %{blocking_user: blocker})
937       |> Enum.map(fn act -> act.id end)
938
939     assert Enum.member?(activities, activity_one.id)
940     refute Enum.member?(activities, activity_two.id)
941     refute Enum.member?(activities, activity_three.id)
942   end
943
944   test "doesn't return announce activities with blocked users in 'cc'" do
945     blocker = insert(:user)
946     blockee = insert(:user)
947     friend = insert(:user)
948
949     {:ok, _user_relationship} = User.block(blocker, blockee)
950
951     {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
952
953     {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
954
955     assert object = Pleroma.Object.normalize(activity_two, fetch: false)
956
957     data = %{
958       "actor" => friend.ap_id,
959       "object" => object.data["id"],
960       "context" => object.data["context"],
961       "type" => "Announce",
962       "to" => ["https://www.w3.org/ns/activitystreams#Public"],
963       "cc" => [blockee.ap_id]
964     }
965
966     assert {:ok, activity_three} = ActivityPub.insert(data)
967
968     activities =
969       ActivityPub.fetch_activities([], %{blocking_user: blocker})
970       |> Enum.map(fn act -> act.id end)
971
972     assert Enum.member?(activities, activity_one.id)
973     refute Enum.member?(activities, activity_two.id)
974     refute Enum.member?(activities, activity_three.id)
975   end
976
977   test "doesn't return activities from blocked domains" do
978     domain = "dogwhistle.zone"
979     domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
980     note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
981     activity = insert(:note_activity, %{note: note})
982     user = insert(:user)
983     {:ok, user} = User.block_domain(user, domain)
984
985     activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
986
987     refute activity in activities
988
989     followed_user = insert(:user)
990     CommonAPI.follow(user, followed_user)
991     {:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user)
992
993     activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
994
995     refute repeat_activity in activities
996   end
997
998   test "see your own posts even when they adress actors from blocked domains" do
999     user = insert(:user)
1000
1001     domain = "dogwhistle.zone"
1002     domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
1003
1004     {:ok, user} = User.block_domain(user, domain)
1005
1006     {:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{domain_user.nickname}"})
1007
1008     activities = ActivityPub.fetch_activities([], %{blocking_user: user})
1009
1010     assert Enum.member?(activities, activity)
1011   end
1012
1013   test "does return activities from followed users on blocked domains" do
1014     domain = "meanies.social"
1015     domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
1016     blocker = insert(:user)
1017
1018     {:ok, blocker, domain_user} = User.follow(blocker, domain_user)
1019     {:ok, blocker} = User.block_domain(blocker, domain)
1020
1021     assert User.following?(blocker, domain_user)
1022     assert User.blocks_domain?(blocker, domain_user)
1023     refute User.blocks?(blocker, domain_user)
1024
1025     note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
1026     activity = insert(:note_activity, %{note: note})
1027
1028     activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
1029
1030     assert activity in activities
1031
1032     # And check that if the guy we DO follow boosts someone else from their domain,
1033     # that should be hidden
1034     another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"})
1035     bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}})
1036     bad_activity = insert(:note_activity, %{note: bad_note})
1037     {:ok, repeat_activity} = CommonAPI.repeat(bad_activity.id, domain_user)
1038
1039     activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
1040
1041     refute repeat_activity in activities
1042   end
1043
1044   test "returns your own posts regardless of mute" do
1045     user = insert(:user)
1046     muted = insert(:user)
1047
1048     {:ok, muted_post} = CommonAPI.post(muted, %{status: "Im stupid"})
1049
1050     {:ok, reply} =
1051       CommonAPI.post(user, %{status: "I'm muting you", in_reply_to_status_id: muted_post.id})
1052
1053     {:ok, _} = User.mute(user, muted)
1054
1055     [activity] = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
1056
1057     assert activity.id == reply.id
1058   end
1059
1060   test "doesn't return muted activities" do
1061     activity_one = insert(:note_activity)
1062     activity_two = insert(:note_activity)
1063     activity_three = insert(:note_activity)
1064     user = insert(:user)
1065     booster = insert(:user)
1066
1067     activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
1068     {:ok, _user_relationships} = User.mute(user, activity_one_actor)
1069
1070     activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
1071
1072     assert Enum.member?(activities, activity_two)
1073     assert Enum.member?(activities, activity_three)
1074     refute Enum.member?(activities, activity_one)
1075
1076     # Calling with 'with_muted' will deliver muted activities, too.
1077     activities =
1078       ActivityPub.fetch_activities([], %{
1079         muting_user: user,
1080         with_muted: true,
1081         skip_preload: true
1082       })
1083
1084     assert Enum.member?(activities, activity_two)
1085     assert Enum.member?(activities, activity_three)
1086     assert Enum.member?(activities, activity_one)
1087
1088     {:ok, _user_mute} = User.unmute(user, activity_one_actor)
1089
1090     activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
1091
1092     assert Enum.member?(activities, activity_two)
1093     assert Enum.member?(activities, activity_three)
1094     assert Enum.member?(activities, activity_one)
1095
1096     activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
1097     {:ok, _user_relationships} = User.mute(user, activity_three_actor)
1098     {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
1099     %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
1100     activity_three = Activity.get_by_id(activity_three.id)
1101
1102     activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
1103
1104     assert Enum.member?(activities, activity_two)
1105     refute Enum.member?(activities, activity_three)
1106     refute Enum.member?(activities, boost_activity)
1107     assert Enum.member?(activities, activity_one)
1108
1109     activities = ActivityPub.fetch_activities([], %{muting_user: nil, skip_preload: true})
1110
1111     assert Enum.member?(activities, activity_two)
1112     assert Enum.member?(activities, activity_three)
1113     assert Enum.member?(activities, boost_activity)
1114     assert Enum.member?(activities, activity_one)
1115   end
1116
1117   test "doesn't return thread muted activities" do
1118     user = insert(:user)
1119     _activity_one = insert(:note_activity)
1120     note_two = insert(:note, data: %{"context" => "suya.."})
1121     activity_two = insert(:note_activity, note: note_two)
1122
1123     {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
1124
1125     assert [_activity_one] = ActivityPub.fetch_activities([], %{muting_user: user})
1126   end
1127
1128   test "returns thread muted activities when with_muted is set" do
1129     user = insert(:user)
1130     _activity_one = insert(:note_activity)
1131     note_two = insert(:note, data: %{"context" => "suya.."})
1132     activity_two = insert(:note_activity, note: note_two)
1133
1134     {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
1135
1136     assert [_activity_two, _activity_one] =
1137              ActivityPub.fetch_activities([], %{muting_user: user, with_muted: true})
1138   end
1139
1140   test "does include announces on request" do
1141     activity_three = insert(:note_activity)
1142     user = insert(:user)
1143     booster = insert(:user)
1144
1145     {:ok, user, booster} = User.follow(user, booster)
1146
1147     {:ok, announce} = CommonAPI.repeat(activity_three.id, booster)
1148
1149     [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])
1150
1151     assert announce_activity.id == announce.id
1152   end
1153
1154   test "excludes reblogs on request" do
1155     user = insert(:user)
1156     {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
1157     {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
1158
1159     [activity] = ActivityPub.fetch_user_activities(user, nil, %{exclude_reblogs: true})
1160
1161     assert activity == expected_activity
1162   end
1163
1164   describe "irreversible filters" do
1165     setup do
1166       user = insert(:user)
1167       user_two = insert(:user)
1168
1169       insert(:filter, user: user_two, phrase: "cofe", hide: true)
1170       insert(:filter, user: user_two, phrase: "ok boomer", hide: true)
1171       insert(:filter, user: user_two, phrase: "test", hide: false)
1172
1173       params = %{
1174         type: ["Create", "Announce"],
1175         user: user_two
1176       }
1177
1178       {:ok, %{user: user, user_two: user_two, params: params}}
1179     end
1180
1181     test "it returns statuses if they don't contain exact filter words", %{
1182       user: user,
1183       params: params
1184     } do
1185       {:ok, _} = CommonAPI.post(user, %{status: "hey"})
1186       {:ok, _} = CommonAPI.post(user, %{status: "got cofefe?"})
1187       {:ok, _} = CommonAPI.post(user, %{status: "I am not a boomer"})
1188       {:ok, _} = CommonAPI.post(user, %{status: "ok boomers"})
1189       {:ok, _} = CommonAPI.post(user, %{status: "ccofee is not a word"})
1190       {:ok, _} = CommonAPI.post(user, %{status: "this is a test"})
1191
1192       activities = ActivityPub.fetch_activities([], params)
1193
1194       assert Enum.count(activities) == 6
1195     end
1196
1197     test "it does not filter user's own statuses", %{user_two: user_two, params: params} do
1198       {:ok, _} = CommonAPI.post(user_two, %{status: "Give me some cofe!"})
1199       {:ok, _} = CommonAPI.post(user_two, %{status: "ok boomer"})
1200
1201       activities = ActivityPub.fetch_activities([], params)
1202
1203       assert Enum.count(activities) == 2
1204     end
1205
1206     test "it excludes statuses with filter words", %{user: user, params: params} do
1207       {:ok, _} = CommonAPI.post(user, %{status: "Give me some cofe!"})
1208       {:ok, _} = CommonAPI.post(user, %{status: "ok boomer"})
1209       {:ok, _} = CommonAPI.post(user, %{status: "is it a cOfE?"})
1210       {:ok, _} = CommonAPI.post(user, %{status: "cofe is all I need"})
1211       {:ok, _} = CommonAPI.post(user, %{status: "— ok BOOMER\n"})
1212
1213       activities = ActivityPub.fetch_activities([], params)
1214
1215       assert Enum.empty?(activities)
1216     end
1217
1218     test "it returns all statuses if user does not have any filters" do
1219       another_user = insert(:user)
1220       {:ok, _} = CommonAPI.post(another_user, %{status: "got cofe?"})
1221       {:ok, _} = CommonAPI.post(another_user, %{status: "test!"})
1222
1223       activities =
1224         ActivityPub.fetch_activities([], %{
1225           type: ["Create", "Announce"],
1226           user: another_user
1227         })
1228
1229       assert Enum.count(activities) == 2
1230     end
1231   end
1232
1233   describe "public fetch activities" do
1234     test "doesn't retrieve unlisted activities" do
1235       user = insert(:user)
1236
1237       {:ok, _unlisted_activity} = CommonAPI.post(user, %{status: "yeah", visibility: "unlisted"})
1238
1239       {:ok, listed_activity} = CommonAPI.post(user, %{status: "yeah"})
1240
1241       [activity] = ActivityPub.fetch_public_activities()
1242
1243       assert activity == listed_activity
1244     end
1245
1246     test "retrieves public activities" do
1247       _activities = ActivityPub.fetch_public_activities()
1248
1249       %{public: public} = ActivityBuilder.public_and_non_public()
1250
1251       activities = ActivityPub.fetch_public_activities()
1252       assert length(activities) == 1
1253       assert Enum.at(activities, 0) == public
1254     end
1255
1256     test "retrieves a maximum of 20 activities" do
1257       ActivityBuilder.insert_list(10)
1258       expected_activities = ActivityBuilder.insert_list(20)
1259
1260       activities = ActivityPub.fetch_public_activities()
1261
1262       assert collect_ids(activities) == collect_ids(expected_activities)
1263       assert length(activities) == 20
1264     end
1265
1266     test "retrieves ids starting from a since_id" do
1267       activities = ActivityBuilder.insert_list(30)
1268       expected_activities = ActivityBuilder.insert_list(10)
1269       since_id = List.last(activities).id
1270
1271       activities = ActivityPub.fetch_public_activities(%{since_id: since_id})
1272
1273       assert collect_ids(activities) == collect_ids(expected_activities)
1274       assert length(activities) == 10
1275     end
1276
1277     test "retrieves ids up to max_id" do
1278       ActivityBuilder.insert_list(10)
1279       expected_activities = ActivityBuilder.insert_list(20)
1280
1281       %{id: max_id} =
1282         10
1283         |> ActivityBuilder.insert_list()
1284         |> List.first()
1285
1286       activities = ActivityPub.fetch_public_activities(%{max_id: max_id})
1287
1288       assert length(activities) == 20
1289       assert collect_ids(activities) == collect_ids(expected_activities)
1290     end
1291
1292     test "paginates via offset/limit" do
1293       _first_part_activities = ActivityBuilder.insert_list(10)
1294       second_part_activities = ActivityBuilder.insert_list(10)
1295
1296       later_activities = ActivityBuilder.insert_list(10)
1297
1298       activities = ActivityPub.fetch_public_activities(%{page: "2", page_size: "20"}, :offset)
1299
1300       assert length(activities) == 20
1301
1302       assert collect_ids(activities) ==
1303                collect_ids(second_part_activities) ++ collect_ids(later_activities)
1304     end
1305
1306     test "doesn't return reblogs for users for whom reblogs have been muted" do
1307       activity = insert(:note_activity)
1308       user = insert(:user)
1309       booster = insert(:user)
1310       {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
1311
1312       {:ok, activity} = CommonAPI.repeat(activity.id, booster)
1313
1314       activities = ActivityPub.fetch_activities([], %{muting_user: user})
1315
1316       refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
1317     end
1318
1319     test "returns reblogs for users for whom reblogs have not been muted" do
1320       activity = insert(:note_activity)
1321       user = insert(:user)
1322       booster = insert(:user)
1323       {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
1324       {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
1325
1326       {:ok, activity} = CommonAPI.repeat(activity.id, booster)
1327
1328       activities = ActivityPub.fetch_activities([], %{muting_user: user})
1329
1330       assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
1331     end
1332   end
1333
1334   describe "uploading files" do
1335     setup do
1336       test_file = %Plug.Upload{
1337         content_type: "image/jpeg",
1338         path: Path.absname("test/fixtures/image.jpg"),
1339         filename: "an_image.jpg"
1340       }
1341
1342       %{test_file: test_file}
1343     end
1344
1345     test "strips / from filename", %{test_file: file} do
1346       file = %Plug.Upload{file | filename: "../../../../../nested/bad.jpg"}
1347       {:ok, %Object{} = object} = ActivityPub.upload(file)
1348       [%{"href" => href}] = object.data["url"]
1349       assert Regex.match?(~r"/bad.jpg$", href)
1350       refute Regex.match?(~r"/nested/", href)
1351     end
1352
1353     test "sets a description if given", %{test_file: file} do
1354       {:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file")
1355       assert object.data["name"] == "a cool file"
1356     end
1357
1358     test "it sets the default description depending on the configuration", %{test_file: file} do
1359       clear_config([Pleroma.Upload, :default_description])
1360
1361       clear_config([Pleroma.Upload, :default_description], nil)
1362       {:ok, %Object{} = object} = ActivityPub.upload(file)
1363       assert object.data["name"] == ""
1364
1365       clear_config([Pleroma.Upload, :default_description], :filename)
1366       {:ok, %Object{} = object} = ActivityPub.upload(file)
1367       assert object.data["name"] == "an_image.jpg"
1368
1369       clear_config([Pleroma.Upload, :default_description], "unnamed attachment")
1370       {:ok, %Object{} = object} = ActivityPub.upload(file)
1371       assert object.data["name"] == "unnamed attachment"
1372     end
1373
1374     test "copies the file to the configured folder", %{test_file: file} do
1375       clear_config([Pleroma.Upload, :default_description], :filename)
1376       {:ok, %Object{} = object} = ActivityPub.upload(file)
1377       assert object.data["name"] == "an_image.jpg"
1378     end
1379
1380     test "works with base64 encoded images" do
1381       file = %{
1382         img: data_uri()
1383       }
1384
1385       {:ok, %Object{}} = ActivityPub.upload(file)
1386     end
1387   end
1388
1389   describe "fetch the latest Follow" do
1390     test "fetches the latest Follow activity" do
1391       %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1392       follower = Repo.get_by(User, ap_id: activity.data["actor"])
1393       followed = Repo.get_by(User, ap_id: activity.data["object"])
1394
1395       assert activity == Utils.fetch_latest_follow(follower, followed)
1396     end
1397   end
1398
1399   describe "unfollowing" do
1400     test "it reverts unfollow activity" do
1401       follower = insert(:user)
1402       followed = insert(:user)
1403
1404       {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1405
1406       with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1407         assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1408       end
1409
1410       activity = Activity.get_by_id(follow_activity.id)
1411       assert activity.data["type"] == "Follow"
1412       assert activity.data["actor"] == follower.ap_id
1413
1414       assert activity.data["object"] == followed.ap_id
1415     end
1416
1417     test "creates an undo activity for the last follow" do
1418       follower = insert(:user)
1419       followed = insert(:user)
1420
1421       {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1422       {:ok, activity} = ActivityPub.unfollow(follower, followed)
1423
1424       assert activity.data["type"] == "Undo"
1425       assert activity.data["actor"] == follower.ap_id
1426
1427       embedded_object = activity.data["object"]
1428       assert is_map(embedded_object)
1429       assert embedded_object["type"] == "Follow"
1430       assert embedded_object["object"] == followed.ap_id
1431       assert embedded_object["id"] == follow_activity.data["id"]
1432     end
1433
1434     test "creates an undo activity for a pending follow request" do
1435       follower = insert(:user)
1436       followed = insert(:user, %{is_locked: true})
1437
1438       {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1439       {:ok, activity} = ActivityPub.unfollow(follower, followed)
1440
1441       assert activity.data["type"] == "Undo"
1442       assert activity.data["actor"] == follower.ap_id
1443
1444       embedded_object = activity.data["object"]
1445       assert is_map(embedded_object)
1446       assert embedded_object["type"] == "Follow"
1447       assert embedded_object["object"] == followed.ap_id
1448       assert embedded_object["id"] == follow_activity.data["id"]
1449     end
1450   end
1451
1452   describe "timeline post-processing" do
1453     test "it filters broken threads" do
1454       user1 = insert(:user)
1455       user2 = insert(:user)
1456       user3 = insert(:user)
1457
1458       {:ok, user1, user3} = User.follow(user1, user3)
1459       assert User.following?(user1, user3)
1460
1461       {:ok, user2, user3} = User.follow(user2, user3)
1462       assert User.following?(user2, user3)
1463
1464       {:ok, user3, user2} = User.follow(user3, user2)
1465       assert User.following?(user3, user2)
1466
1467       {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"})
1468
1469       {:ok, private_activity_1} = CommonAPI.post(user3, %{status: "hi 2", visibility: "private"})
1470
1471       {:ok, private_activity_2} =
1472         CommonAPI.post(user2, %{
1473           status: "hi 3",
1474           visibility: "private",
1475           in_reply_to_status_id: private_activity_1.id
1476         })
1477
1478       {:ok, private_activity_3} =
1479         CommonAPI.post(user3, %{
1480           status: "hi 4",
1481           visibility: "private",
1482           in_reply_to_status_id: private_activity_2.id
1483         })
1484
1485       activities =
1486         ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1487         |> Enum.map(fn a -> a.id end)
1488
1489       private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1490
1491       assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1492
1493       assert length(activities) == 3
1494
1495       activities =
1496         ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{user: user1})
1497         |> Enum.map(fn a -> a.id end)
1498
1499       assert [public_activity.id, private_activity_1.id] == activities
1500       assert length(activities) == 2
1501     end
1502   end
1503
1504   describe "flag/1" do
1505     setup do
1506       reporter = insert(:user)
1507       target_account = insert(:user)
1508       content = "foobar"
1509       {:ok, activity} = CommonAPI.post(target_account, %{status: content})
1510       context = Utils.generate_context_id()
1511
1512       reporter_ap_id = reporter.ap_id
1513       target_ap_id = target_account.ap_id
1514       activity_ap_id = activity.data["id"]
1515       object_ap_id = activity.object.data["id"]
1516
1517       activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1518
1519       {:ok,
1520        %{
1521          reporter: reporter,
1522          context: context,
1523          target_account: target_account,
1524          reported_activity: activity,
1525          content: content,
1526          activity_ap_id: activity_ap_id,
1527          object_ap_id: object_ap_id,
1528          activity_with_object: activity_with_object,
1529          reporter_ap_id: reporter_ap_id,
1530          target_ap_id: target_ap_id
1531        }}
1532     end
1533
1534     test "it can create a Flag activity",
1535          %{
1536            reporter: reporter,
1537            context: context,
1538            target_account: target_account,
1539            reported_activity: reported_activity,
1540            content: content,
1541            object_ap_id: object_ap_id,
1542            activity_with_object: activity_with_object,
1543            reporter_ap_id: reporter_ap_id,
1544            target_ap_id: target_ap_id
1545          } do
1546       assert {:ok, activity} =
1547                ActivityPub.flag(%{
1548                  actor: reporter,
1549                  context: context,
1550                  account: target_account,
1551                  statuses: [reported_activity],
1552                  content: content
1553                })
1554
1555       note_obj = %{
1556         "type" => "Note",
1557         "id" => object_ap_id,
1558         "content" => content,
1559         "published" => activity_with_object.object.data["published"],
1560         "actor" =>
1561           AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
1562       }
1563
1564       assert %Activity{
1565                actor: ^reporter_ap_id,
1566                data: %{
1567                  "type" => "Flag",
1568                  "content" => ^content,
1569                  "context" => ^context,
1570                  "object" => [^target_ap_id, ^note_obj]
1571                }
1572              } = activity
1573     end
1574
1575     test_with_mock "strips status data from Flag, before federating it",
1576                    %{
1577                      reporter: reporter,
1578                      context: context,
1579                      target_account: target_account,
1580                      reported_activity: reported_activity,
1581                      object_ap_id: object_ap_id,
1582                      content: content
1583                    },
1584                    Utils,
1585                    [:passthrough],
1586                    [] do
1587       {:ok, activity} =
1588         ActivityPub.flag(%{
1589           actor: reporter,
1590           context: context,
1591           account: target_account,
1592           statuses: [reported_activity],
1593           content: content
1594         })
1595
1596       new_data = put_in(activity.data, ["object"], [target_account.ap_id, object_ap_id])
1597
1598       assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1599     end
1600
1601     test_with_mock "reverts on error",
1602                    %{
1603                      reporter: reporter,
1604                      context: context,
1605                      target_account: target_account,
1606                      reported_activity: reported_activity,
1607                      content: content
1608                    },
1609                    Utils,
1610                    [:passthrough],
1611                    maybe_federate: fn _ -> {:error, :reverted} end do
1612       assert {:error, :reverted} =
1613                ActivityPub.flag(%{
1614                  actor: reporter,
1615                  context: context,
1616                  account: target_account,
1617                  statuses: [reported_activity],
1618                  content: content
1619                })
1620
1621       assert Repo.aggregate(Activity, :count, :id) == 1
1622       assert Repo.aggregate(Object, :count, :id) == 1
1623       assert Repo.aggregate(Notification, :count, :id) == 0
1624     end
1625   end
1626
1627   test "fetch_activities/2 returns activities addressed to a list " do
1628     user = insert(:user)
1629     member = insert(:user)
1630     {:ok, list} = Pleroma.List.create("foo", user)
1631     {:ok, list} = Pleroma.List.follow(list, member)
1632
1633     {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1634
1635     activity = Repo.preload(activity, :bookmark)
1636     activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1637
1638     assert ActivityPub.fetch_activities([], %{user: user}) == [activity]
1639   end
1640
1641   def data_uri do
1642     File.read!("test/fixtures/avatar_data_uri")
1643   end
1644
1645   describe "fetch_activities_bounded" do
1646     test "fetches private posts for followed users" do
1647       user = insert(:user)
1648
1649       {:ok, activity} =
1650         CommonAPI.post(user, %{
1651           status: "thought I looked cute might delete later :3",
1652           visibility: "private"
1653         })
1654
1655       [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1656       assert result.id == activity.id
1657     end
1658
1659     test "fetches only public posts for other users" do
1660       user = insert(:user)
1661       {:ok, activity} = CommonAPI.post(user, %{status: "#cofe", visibility: "public"})
1662
1663       {:ok, _private_activity} =
1664         CommonAPI.post(user, %{
1665           status: "why is tenshi eating a corndog so cute?",
1666           visibility: "private"
1667         })
1668
1669       [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1670       assert result.id == activity.id
1671     end
1672   end
1673
1674   describe "fetch_follow_information_for_user" do
1675     test "synchronizes following/followers counters" do
1676       user =
1677         insert(:user,
1678           local: false,
1679           follower_address: "http://localhost:4001/users/fuser2/followers",
1680           following_address: "http://localhost:4001/users/fuser2/following"
1681         )
1682
1683       {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1684       assert info.follower_count == 527
1685       assert info.following_count == 267
1686     end
1687
1688     test "detects hidden followers" do
1689       mock(fn env ->
1690         case env.url do
1691           "http://localhost:4001/users/masto_closed/followers?page=1" ->
1692             %Tesla.Env{status: 403, body: ""}
1693
1694           _ ->
1695             apply(HttpRequestMock, :request, [env])
1696         end
1697       end)
1698
1699       user =
1700         insert(:user,
1701           local: false,
1702           follower_address: "http://localhost:4001/users/masto_closed/followers",
1703           following_address: "http://localhost:4001/users/masto_closed/following"
1704         )
1705
1706       {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1707       assert follow_info.hide_followers == true
1708       assert follow_info.hide_follows == false
1709     end
1710
1711     test "detects hidden follows" do
1712       mock(fn env ->
1713         case env.url do
1714           "http://localhost:4001/users/masto_closed/following?page=1" ->
1715             %Tesla.Env{status: 403, body: ""}
1716
1717           _ ->
1718             apply(HttpRequestMock, :request, [env])
1719         end
1720       end)
1721
1722       user =
1723         insert(:user,
1724           local: false,
1725           follower_address: "http://localhost:4001/users/masto_closed/followers",
1726           following_address: "http://localhost:4001/users/masto_closed/following"
1727         )
1728
1729       {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1730       assert follow_info.hide_followers == false
1731       assert follow_info.hide_follows == true
1732     end
1733
1734     test "detects hidden follows/followers for friendica" do
1735       user =
1736         insert(:user,
1737           local: false,
1738           follower_address: "http://localhost:8080/followers/fuser3",
1739           following_address: "http://localhost:8080/following/fuser3"
1740         )
1741
1742       {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1743       assert follow_info.hide_followers == true
1744       assert follow_info.follower_count == 296
1745       assert follow_info.following_count == 32
1746       assert follow_info.hide_follows == true
1747     end
1748
1749     test "doesn't crash when follower and following counters are hidden" do
1750       mock(fn env ->
1751         case env.url do
1752           "http://localhost:4001/users/masto_hidden_counters/following" ->
1753             json(
1754               %{
1755                 "@context" => "https://www.w3.org/ns/activitystreams",
1756                 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1757               },
1758               headers: HttpRequestMock.activitypub_object_headers()
1759             )
1760
1761           "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1762             %Tesla.Env{status: 403, body: ""}
1763
1764           "http://localhost:4001/users/masto_hidden_counters/followers" ->
1765             json(
1766               %{
1767                 "@context" => "https://www.w3.org/ns/activitystreams",
1768                 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1769               },
1770               headers: HttpRequestMock.activitypub_object_headers()
1771             )
1772
1773           "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1774             %Tesla.Env{status: 403, body: ""}
1775         end
1776       end)
1777
1778       user =
1779         insert(:user,
1780           local: false,
1781           follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1782           following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1783         )
1784
1785       {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1786
1787       assert follow_info.hide_followers == true
1788       assert follow_info.follower_count == 0
1789       assert follow_info.hide_follows == true
1790       assert follow_info.following_count == 0
1791     end
1792   end
1793
1794   describe "fetch_favourites/3" do
1795     test "returns a favourite activities sorted by adds to favorite" do
1796       user = insert(:user)
1797       other_user = insert(:user)
1798       user1 = insert(:user)
1799       user2 = insert(:user)
1800       {:ok, a1} = CommonAPI.post(user1, %{status: "bla"})
1801       {:ok, _a2} = CommonAPI.post(user2, %{status: "traps are happy"})
1802       {:ok, a3} = CommonAPI.post(user2, %{status: "Trees Are "})
1803       {:ok, a4} = CommonAPI.post(user2, %{status: "Agent Smith "})
1804       {:ok, a5} = CommonAPI.post(user1, %{status: "Red or Blue "})
1805
1806       {:ok, _} = CommonAPI.favorite(user, a4.id)
1807       {:ok, _} = CommonAPI.favorite(other_user, a3.id)
1808       {:ok, _} = CommonAPI.favorite(user, a3.id)
1809       {:ok, _} = CommonAPI.favorite(other_user, a5.id)
1810       {:ok, _} = CommonAPI.favorite(user, a5.id)
1811       {:ok, _} = CommonAPI.favorite(other_user, a4.id)
1812       {:ok, _} = CommonAPI.favorite(user, a1.id)
1813       {:ok, _} = CommonAPI.favorite(other_user, a1.id)
1814       result = ActivityPub.fetch_favourites(user)
1815
1816       assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1817
1818       result = ActivityPub.fetch_favourites(user, %{limit: 2})
1819       assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1820     end
1821   end
1822
1823   describe "Move activity" do
1824     test "create" do
1825       %{ap_id: old_ap_id} = old_user = insert(:user)
1826       %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1827       follower = insert(:user)
1828       follower_move_opted_out = insert(:user, allow_following_move: false)
1829
1830       User.follow(follower, old_user)
1831       User.follow(follower_move_opted_out, old_user)
1832
1833       assert User.following?(follower, old_user)
1834       assert User.following?(follower_move_opted_out, old_user)
1835
1836       assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1837
1838       assert %Activity{
1839                actor: ^old_ap_id,
1840                data: %{
1841                  "actor" => ^old_ap_id,
1842                  "object" => ^old_ap_id,
1843                  "target" => ^new_ap_id,
1844                  "type" => "Move"
1845                },
1846                local: true,
1847                recipients: recipients
1848              } = activity
1849
1850       assert old_user.follower_address in recipients
1851
1852       params = %{
1853         "op" => "move_following",
1854         "origin_id" => old_user.id,
1855         "target_id" => new_user.id
1856       }
1857
1858       assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1859
1860       Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
1861
1862       refute User.following?(follower, old_user)
1863       assert User.following?(follower, new_user)
1864
1865       assert User.following?(follower_move_opted_out, old_user)
1866       refute User.following?(follower_move_opted_out, new_user)
1867
1868       activity = %Activity{activity | object: nil}
1869
1870       assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1871
1872       assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1873     end
1874
1875     test "old user must be in the new user's `also_known_as` list" do
1876       old_user = insert(:user)
1877       new_user = insert(:user)
1878
1879       assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1880                ActivityPub.move(old_user, new_user)
1881     end
1882
1883     test "do not move remote user following relationships" do
1884       %{ap_id: old_ap_id} = old_user = insert(:user)
1885       %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1886       follower_remote = insert(:user, local: false)
1887
1888       User.follow(follower_remote, old_user)
1889
1890       assert User.following?(follower_remote, old_user)
1891
1892       assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1893
1894       assert %Activity{
1895                actor: ^old_ap_id,
1896                data: %{
1897                  "actor" => ^old_ap_id,
1898                  "object" => ^old_ap_id,
1899                  "target" => ^new_ap_id,
1900                  "type" => "Move"
1901                },
1902                local: true
1903              } = activity
1904
1905       params = %{
1906         "op" => "move_following",
1907         "origin_id" => old_user.id,
1908         "target_id" => new_user.id
1909       }
1910
1911       assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1912
1913       Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
1914
1915       assert User.following?(follower_remote, old_user)
1916       refute User.following?(follower_remote, new_user)
1917     end
1918   end
1919
1920   test "doesn't retrieve replies activities with exclude_replies" do
1921     user = insert(:user)
1922
1923     {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
1924
1925     {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id})
1926
1927     [result] = ActivityPub.fetch_public_activities(%{exclude_replies: true})
1928
1929     assert result.id == activity.id
1930
1931     assert length(ActivityPub.fetch_public_activities()) == 2
1932   end
1933
1934   describe "replies filtering with public messages" do
1935     setup :public_messages
1936
1937     test "public timeline", %{users: %{u1: user}} do
1938       activities_ids =
1939         %{}
1940         |> Map.put(:type, ["Create", "Announce"])
1941         |> Map.put(:local_only, false)
1942         |> Map.put(:blocking_user, user)
1943         |> Map.put(:muting_user, user)
1944         |> Map.put(:reply_filtering_user, user)
1945         |> ActivityPub.fetch_public_activities()
1946         |> Enum.map(& &1.id)
1947
1948       assert length(activities_ids) == 16
1949     end
1950
1951     test "public timeline with reply_visibility `following`", %{
1952       users: %{u1: user},
1953       u1: u1,
1954       u2: u2,
1955       u3: u3,
1956       u4: u4,
1957       activities: activities
1958     } do
1959       activities_ids =
1960         %{}
1961         |> Map.put(:type, ["Create", "Announce"])
1962         |> Map.put(:local_only, false)
1963         |> Map.put(:blocking_user, user)
1964         |> Map.put(:muting_user, user)
1965         |> Map.put(:reply_visibility, "following")
1966         |> Map.put(:reply_filtering_user, user)
1967         |> ActivityPub.fetch_public_activities()
1968         |> Enum.map(& &1.id)
1969
1970       assert length(activities_ids) == 14
1971
1972       visible_ids =
1973         Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
1974
1975       assert Enum.all?(visible_ids, &(&1 in activities_ids))
1976     end
1977
1978     test "public timeline with reply_visibility `self`", %{
1979       users: %{u1: user},
1980       u1: u1,
1981       u2: u2,
1982       u3: u3,
1983       u4: u4,
1984       activities: activities
1985     } do
1986       activities_ids =
1987         %{}
1988         |> Map.put(:type, ["Create", "Announce"])
1989         |> Map.put(:local_only, false)
1990         |> Map.put(:blocking_user, user)
1991         |> Map.put(:muting_user, user)
1992         |> Map.put(:reply_visibility, "self")
1993         |> Map.put(:reply_filtering_user, user)
1994         |> ActivityPub.fetch_public_activities()
1995         |> Enum.map(& &1.id)
1996
1997       assert length(activities_ids) == 10
1998       visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
1999       assert Enum.all?(visible_ids, &(&1 in activities_ids))
2000     end
2001
2002     test "home timeline", %{
2003       users: %{u1: user},
2004       activities: activities,
2005       u1: u1,
2006       u2: u2,
2007       u3: u3,
2008       u4: u4
2009     } do
2010       params =
2011         %{}
2012         |> Map.put(:type, ["Create", "Announce"])
2013         |> Map.put(:blocking_user, user)
2014         |> Map.put(:muting_user, user)
2015         |> Map.put(:user, user)
2016         |> Map.put(:reply_filtering_user, user)
2017
2018       activities_ids =
2019         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2020         |> Enum.map(& &1.id)
2021
2022       assert length(activities_ids) == 13
2023
2024       visible_ids =
2025         Map.values(u1) ++
2026           Map.values(u3) ++
2027           [
2028             activities[:a1],
2029             activities[:a2],
2030             activities[:a4],
2031             u2[:r1],
2032             u2[:r3],
2033             u4[:r1],
2034             u4[:r2]
2035           ]
2036
2037       assert Enum.all?(visible_ids, &(&1 in activities_ids))
2038     end
2039
2040     test "home timeline with reply_visibility `following`", %{
2041       users: %{u1: user},
2042       activities: activities,
2043       u1: u1,
2044       u2: u2,
2045       u3: u3,
2046       u4: u4
2047     } do
2048       params =
2049         %{}
2050         |> Map.put(:type, ["Create", "Announce"])
2051         |> Map.put(:blocking_user, user)
2052         |> Map.put(:muting_user, user)
2053         |> Map.put(:user, user)
2054         |> Map.put(:reply_visibility, "following")
2055         |> Map.put(:reply_filtering_user, user)
2056
2057       activities_ids =
2058         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2059         |> Enum.map(& &1.id)
2060
2061       assert length(activities_ids) == 11
2062
2063       visible_ids =
2064         Map.values(u1) ++
2065           [
2066             activities[:a1],
2067             activities[:a2],
2068             activities[:a4],
2069             u2[:r1],
2070             u2[:r3],
2071             u3[:r1],
2072             u4[:r1],
2073             u4[:r2]
2074           ]
2075
2076       assert Enum.all?(visible_ids, &(&1 in activities_ids))
2077     end
2078
2079     test "home timeline with reply_visibility `self`", %{
2080       users: %{u1: user},
2081       activities: activities,
2082       u1: u1,
2083       u2: u2,
2084       u3: u3,
2085       u4: u4
2086     } do
2087       params =
2088         %{}
2089         |> Map.put(:type, ["Create", "Announce"])
2090         |> Map.put(:blocking_user, user)
2091         |> Map.put(:muting_user, user)
2092         |> Map.put(:user, user)
2093         |> Map.put(:reply_visibility, "self")
2094         |> Map.put(:reply_filtering_user, user)
2095
2096       activities_ids =
2097         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2098         |> Enum.map(& &1.id)
2099
2100       assert length(activities_ids) == 9
2101
2102       visible_ids =
2103         Map.values(u1) ++
2104           [
2105             activities[:a1],
2106             activities[:a2],
2107             activities[:a4],
2108             u2[:r1],
2109             u3[:r1],
2110             u4[:r1]
2111           ]
2112
2113       assert Enum.all?(visible_ids, &(&1 in activities_ids))
2114     end
2115
2116     test "filtering out announces where the user is the actor of the announced message" do
2117       user = insert(:user)
2118       other_user = insert(:user)
2119       third_user = insert(:user)
2120       User.follow(user, other_user)
2121
2122       {:ok, post} = CommonAPI.post(user, %{status: "yo"})
2123       {:ok, other_post} = CommonAPI.post(third_user, %{status: "yo"})
2124       {:ok, _announce} = CommonAPI.repeat(post.id, other_user)
2125       {:ok, _announce} = CommonAPI.repeat(post.id, third_user)
2126       {:ok, announce} = CommonAPI.repeat(other_post.id, other_user)
2127
2128       params = %{
2129         type: ["Announce"]
2130       }
2131
2132       results =
2133         [user.ap_id | User.following(user)]
2134         |> ActivityPub.fetch_activities(params)
2135
2136       assert length(results) == 3
2137
2138       params = %{
2139         type: ["Announce"],
2140         announce_filtering_user: user
2141       }
2142
2143       [result] =
2144         [user.ap_id | User.following(user)]
2145         |> ActivityPub.fetch_activities(params)
2146
2147       assert result.id == announce.id
2148     end
2149   end
2150
2151   describe "replies filtering with private messages" do
2152     setup :private_messages
2153
2154     test "public timeline", %{users: %{u1: user}} do
2155       activities_ids =
2156         %{}
2157         |> Map.put(:type, ["Create", "Announce"])
2158         |> Map.put(:local_only, false)
2159         |> Map.put(:blocking_user, user)
2160         |> Map.put(:muting_user, user)
2161         |> Map.put(:user, user)
2162         |> ActivityPub.fetch_public_activities()
2163         |> Enum.map(& &1.id)
2164
2165       assert activities_ids == []
2166     end
2167
2168     test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2169       activities_ids =
2170         %{}
2171         |> Map.put(:type, ["Create", "Announce"])
2172         |> Map.put(:local_only, false)
2173         |> Map.put(:blocking_user, user)
2174         |> Map.put(:muting_user, user)
2175         |> Map.put(:reply_visibility, "following")
2176         |> Map.put(:reply_filtering_user, user)
2177         |> Map.put(:user, user)
2178         |> ActivityPub.fetch_public_activities()
2179         |> Enum.map(& &1.id)
2180
2181       assert activities_ids == []
2182     end
2183
2184     test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
2185       activities_ids =
2186         %{}
2187         |> Map.put(:type, ["Create", "Announce"])
2188         |> Map.put(:local_only, false)
2189         |> Map.put(:blocking_user, user)
2190         |> Map.put(:muting_user, user)
2191         |> Map.put(:reply_visibility, "self")
2192         |> Map.put(:reply_filtering_user, user)
2193         |> Map.put(:user, user)
2194         |> ActivityPub.fetch_public_activities()
2195         |> Enum.map(& &1.id)
2196
2197       assert activities_ids == []
2198
2199       activities_ids =
2200         %{}
2201         |> Map.put(:reply_visibility, "self")
2202         |> Map.put(:reply_filtering_user, nil)
2203         |> ActivityPub.fetch_public_activities()
2204
2205       assert activities_ids == []
2206     end
2207
2208     test "home timeline", %{users: %{u1: user}} do
2209       params =
2210         %{}
2211         |> Map.put(:type, ["Create", "Announce"])
2212         |> Map.put(:blocking_user, user)
2213         |> Map.put(:muting_user, user)
2214         |> Map.put(:user, user)
2215
2216       activities_ids =
2217         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2218         |> Enum.map(& &1.id)
2219
2220       assert length(activities_ids) == 12
2221     end
2222
2223     test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2224       params =
2225         %{}
2226         |> Map.put(:type, ["Create", "Announce"])
2227         |> Map.put(:blocking_user, user)
2228         |> Map.put(:muting_user, user)
2229         |> Map.put(:user, user)
2230         |> Map.put(:reply_visibility, "following")
2231         |> Map.put(:reply_filtering_user, user)
2232
2233       activities_ids =
2234         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2235         |> Enum.map(& &1.id)
2236
2237       assert length(activities_ids) == 12
2238     end
2239
2240     test "home timeline with default reply_visibility `self`", %{
2241       users: %{u1: user},
2242       activities: activities,
2243       u1: u1,
2244       u2: u2,
2245       u3: u3,
2246       u4: u4
2247     } do
2248       params =
2249         %{}
2250         |> Map.put(:type, ["Create", "Announce"])
2251         |> Map.put(:blocking_user, user)
2252         |> Map.put(:muting_user, user)
2253         |> Map.put(:user, user)
2254         |> Map.put(:reply_visibility, "self")
2255         |> Map.put(:reply_filtering_user, user)
2256
2257       activities_ids =
2258         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2259         |> Enum.map(& &1.id)
2260
2261       assert length(activities_ids) == 10
2262
2263       visible_ids =
2264         Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
2265
2266       assert Enum.all?(visible_ids, &(&1 in activities_ids))
2267     end
2268   end
2269
2270   defp public_messages(_) do
2271     [u1, u2, u3, u4] = insert_list(4, :user)
2272     {:ok, u1, u2} = User.follow(u1, u2)
2273     {:ok, u2, u1} = User.follow(u2, u1)
2274     {:ok, u1, u4} = User.follow(u1, u4)
2275     {:ok, u4, u1} = User.follow(u4, u1)
2276
2277     {:ok, u2, u3} = User.follow(u2, u3)
2278     {:ok, u3, u2} = User.follow(u3, u2)
2279
2280     {:ok, a1} = CommonAPI.post(u1, %{status: "Status"})
2281
2282     {:ok, r1_1} =
2283       CommonAPI.post(u2, %{
2284         status: "@#{u1.nickname} reply from u2 to u1",
2285         in_reply_to_status_id: a1.id
2286       })
2287
2288     {:ok, r1_2} =
2289       CommonAPI.post(u3, %{
2290         status: "@#{u1.nickname} reply from u3 to u1",
2291         in_reply_to_status_id: a1.id
2292       })
2293
2294     {:ok, r1_3} =
2295       CommonAPI.post(u4, %{
2296         status: "@#{u1.nickname} reply from u4 to u1",
2297         in_reply_to_status_id: a1.id
2298       })
2299
2300     {:ok, a2} = CommonAPI.post(u2, %{status: "Status"})
2301
2302     {:ok, r2_1} =
2303       CommonAPI.post(u1, %{
2304         status: "@#{u2.nickname} reply from u1 to u2",
2305         in_reply_to_status_id: a2.id
2306       })
2307
2308     {:ok, r2_2} =
2309       CommonAPI.post(u3, %{
2310         status: "@#{u2.nickname} reply from u3 to u2",
2311         in_reply_to_status_id: a2.id
2312       })
2313
2314     {:ok, r2_3} =
2315       CommonAPI.post(u4, %{
2316         status: "@#{u2.nickname} reply from u4 to u2",
2317         in_reply_to_status_id: a2.id
2318       })
2319
2320     {:ok, a3} = CommonAPI.post(u3, %{status: "Status"})
2321
2322     {:ok, r3_1} =
2323       CommonAPI.post(u1, %{
2324         status: "@#{u3.nickname} reply from u1 to u3",
2325         in_reply_to_status_id: a3.id
2326       })
2327
2328     {:ok, r3_2} =
2329       CommonAPI.post(u2, %{
2330         status: "@#{u3.nickname} reply from u2 to u3",
2331         in_reply_to_status_id: a3.id
2332       })
2333
2334     {:ok, r3_3} =
2335       CommonAPI.post(u4, %{
2336         status: "@#{u3.nickname} reply from u4 to u3",
2337         in_reply_to_status_id: a3.id
2338       })
2339
2340     {:ok, a4} = CommonAPI.post(u4, %{status: "Status"})
2341
2342     {:ok, r4_1} =
2343       CommonAPI.post(u1, %{
2344         status: "@#{u4.nickname} reply from u1 to u4",
2345         in_reply_to_status_id: a4.id
2346       })
2347
2348     {:ok, r4_2} =
2349       CommonAPI.post(u2, %{
2350         status: "@#{u4.nickname} reply from u2 to u4",
2351         in_reply_to_status_id: a4.id
2352       })
2353
2354     {:ok, r4_3} =
2355       CommonAPI.post(u3, %{
2356         status: "@#{u4.nickname} reply from u3 to u4",
2357         in_reply_to_status_id: a4.id
2358       })
2359
2360     {:ok,
2361      users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2362      activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2363      u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2364      u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
2365      u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
2366      u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
2367   end
2368
2369   defp private_messages(_) do
2370     [u1, u2, u3, u4] = insert_list(4, :user)
2371     {:ok, u1, u2} = User.follow(u1, u2)
2372     {:ok, u2, u1} = User.follow(u2, u1)
2373     {:ok, u1, u3} = User.follow(u1, u3)
2374     {:ok, u3, u1} = User.follow(u3, u1)
2375     {:ok, u1, u4} = User.follow(u1, u4)
2376     {:ok, u4, u1} = User.follow(u4, u1)
2377
2378     {:ok, u2, u3} = User.follow(u2, u3)
2379     {:ok, u3, u2} = User.follow(u3, u2)
2380
2381     {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"})
2382
2383     {:ok, r1_1} =
2384       CommonAPI.post(u2, %{
2385         status: "@#{u1.nickname} reply from u2 to u1",
2386         in_reply_to_status_id: a1.id,
2387         visibility: "private"
2388       })
2389
2390     {:ok, r1_2} =
2391       CommonAPI.post(u3, %{
2392         status: "@#{u1.nickname} reply from u3 to u1",
2393         in_reply_to_status_id: a1.id,
2394         visibility: "private"
2395       })
2396
2397     {:ok, r1_3} =
2398       CommonAPI.post(u4, %{
2399         status: "@#{u1.nickname} reply from u4 to u1",
2400         in_reply_to_status_id: a1.id,
2401         visibility: "private"
2402       })
2403
2404     {:ok, a2} = CommonAPI.post(u2, %{status: "Status", visibility: "private"})
2405
2406     {:ok, r2_1} =
2407       CommonAPI.post(u1, %{
2408         status: "@#{u2.nickname} reply from u1 to u2",
2409         in_reply_to_status_id: a2.id,
2410         visibility: "private"
2411       })
2412
2413     {:ok, r2_2} =
2414       CommonAPI.post(u3, %{
2415         status: "@#{u2.nickname} reply from u3 to u2",
2416         in_reply_to_status_id: a2.id,
2417         visibility: "private"
2418       })
2419
2420     {:ok, a3} = CommonAPI.post(u3, %{status: "Status", visibility: "private"})
2421
2422     {:ok, r3_1} =
2423       CommonAPI.post(u1, %{
2424         status: "@#{u3.nickname} reply from u1 to u3",
2425         in_reply_to_status_id: a3.id,
2426         visibility: "private"
2427       })
2428
2429     {:ok, r3_2} =
2430       CommonAPI.post(u2, %{
2431         status: "@#{u3.nickname} reply from u2 to u3",
2432         in_reply_to_status_id: a3.id,
2433         visibility: "private"
2434       })
2435
2436     {:ok, a4} = CommonAPI.post(u4, %{status: "Status", visibility: "private"})
2437
2438     {:ok, r4_1} =
2439       CommonAPI.post(u1, %{
2440         status: "@#{u4.nickname} reply from u1 to u4",
2441         in_reply_to_status_id: a4.id,
2442         visibility: "private"
2443       })
2444
2445     {:ok,
2446      users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2447      activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2448      u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2449      u2: %{r1: r2_1.id, r2: r2_2.id},
2450      u3: %{r1: r3_1.id, r2: r3_2.id},
2451      u4: %{r1: r4_1.id}}
2452   end
2453
2454   describe "maybe_update_follow_information/1" do
2455     setup do
2456       clear_config([:instance, :external_user_synchronization], true)
2457
2458       user = %{
2459         local: false,
2460         ap_id: "https://gensokyo.2hu/users/raymoo",
2461         following_address: "https://gensokyo.2hu/users/following",
2462         follower_address: "https://gensokyo.2hu/users/followers",
2463         type: "Person"
2464       }
2465
2466       %{user: user}
2467     end
2468
2469     test "logs an error when it can't fetch the info", %{user: user} do
2470       assert capture_log(fn ->
2471                ActivityPub.maybe_update_follow_information(user)
2472              end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2473     end
2474
2475     test "just returns the input if the user type is Application", %{
2476       user: user
2477     } do
2478       user =
2479         user
2480         |> Map.put(:type, "Application")
2481
2482       refute capture_log(fn ->
2483                assert ^user = ActivityPub.maybe_update_follow_information(user)
2484              end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2485     end
2486
2487     test "it just returns the input if the user has no following/follower addresses", %{
2488       user: user
2489     } do
2490       user =
2491         user
2492         |> Map.put(:following_address, nil)
2493         |> Map.put(:follower_address, nil)
2494
2495       refute capture_log(fn ->
2496                assert ^user = ActivityPub.maybe_update_follow_information(user)
2497              end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2498     end
2499   end
2500
2501   describe "global activity expiration" do
2502     test "creates an activity expiration for local Create activities" do
2503       clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy)
2504
2505       {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
2506       {:ok, follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
2507
2508       assert_enqueued(
2509         worker: Pleroma.Workers.PurgeExpiredActivity,
2510         args: %{activity_id: activity.id},
2511         scheduled_at:
2512           activity.inserted_at
2513           |> DateTime.from_naive!("Etc/UTC")
2514           |> Timex.shift(days: 365)
2515       )
2516
2517       refute_enqueued(
2518         worker: Pleroma.Workers.PurgeExpiredActivity,
2519         args: %{activity_id: follow.id}
2520       )
2521     end
2522   end
2523
2524   describe "handling of clashing nicknames" do
2525     test "renames an existing user with a clashing nickname and a different ap id" do
2526       orig_user =
2527         insert(
2528           :user,
2529           local: false,
2530           nickname: "admin@mastodon.example.org",
2531           ap_id: "http://mastodon.example.org/users/harinezumigari"
2532         )
2533
2534       %{
2535         nickname: orig_user.nickname,
2536         ap_id: orig_user.ap_id <> "part_2"
2537       }
2538       |> ActivityPub.maybe_handle_clashing_nickname()
2539
2540       user = User.get_by_id(orig_user.id)
2541
2542       assert user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
2543     end
2544
2545     test "does nothing with a clashing nickname and the same ap id" do
2546       orig_user =
2547         insert(
2548           :user,
2549           local: false,
2550           nickname: "admin@mastodon.example.org",
2551           ap_id: "http://mastodon.example.org/users/harinezumigari"
2552         )
2553
2554       %{
2555         nickname: orig_user.nickname,
2556         ap_id: orig_user.ap_id
2557       }
2558       |> ActivityPub.maybe_handle_clashing_nickname()
2559
2560       user = User.get_by_id(orig_user.id)
2561
2562       assert user.nickname == orig_user.nickname
2563     end
2564   end
2565
2566   describe "reply filtering" do
2567     test "`following` still contains announcements by friends" do
2568       user = insert(:user)
2569       followed = insert(:user)
2570       not_followed = insert(:user)
2571
2572       User.follow(user, followed)
2573
2574       {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2575
2576       {:ok, not_followed_to_followed} =
2577         CommonAPI.post(not_followed, %{
2578           status: "Also hello",
2579           in_reply_to_status_id: followed_post.id
2580         })
2581
2582       {:ok, retoot} = CommonAPI.repeat(not_followed_to_followed.id, followed)
2583
2584       params =
2585         %{}
2586         |> Map.put(:type, ["Create", "Announce"])
2587         |> Map.put(:blocking_user, user)
2588         |> Map.put(:muting_user, user)
2589         |> Map.put(:reply_filtering_user, user)
2590         |> Map.put(:reply_visibility, "following")
2591         |> Map.put(:announce_filtering_user, user)
2592         |> Map.put(:user, user)
2593
2594       activities =
2595         [user.ap_id | User.following(user)]
2596         |> ActivityPub.fetch_activities(params)
2597
2598       followed_post_id = followed_post.id
2599       retoot_id = retoot.id
2600
2601       assert [%{id: ^followed_post_id}, %{id: ^retoot_id}] = activities
2602
2603       assert length(activities) == 2
2604     end
2605
2606     # This test is skipped because, while this is the desired behavior,
2607     # there seems to be no good way to achieve it with the method that
2608     # we currently use for detecting to who a reply is directed.
2609     # This is a TODO and should be fixed by a later rewrite of the code
2610     # in question.
2611     @tag skip: true
2612     test "`following` still contains self-replies by friends" do
2613       user = insert(:user)
2614       followed = insert(:user)
2615       not_followed = insert(:user)
2616
2617       User.follow(user, followed)
2618
2619       {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2620       {:ok, not_followed_post} = CommonAPI.post(not_followed, %{status: "Also hello"})
2621
2622       {:ok, _followed_to_not_followed} =
2623         CommonAPI.post(followed, %{status: "sup", in_reply_to_status_id: not_followed_post.id})
2624
2625       {:ok, _followed_self_reply} =
2626         CommonAPI.post(followed, %{status: "Also cofe", in_reply_to_status_id: followed_post.id})
2627
2628       params =
2629         %{}
2630         |> Map.put(:type, ["Create", "Announce"])
2631         |> Map.put(:blocking_user, user)
2632         |> Map.put(:muting_user, user)
2633         |> Map.put(:reply_filtering_user, user)
2634         |> Map.put(:reply_visibility, "following")
2635         |> Map.put(:announce_filtering_user, user)
2636         |> Map.put(:user, user)
2637
2638       activities =
2639         [user.ap_id | User.following(user)]
2640         |> ActivityPub.fetch_activities(params)
2641
2642       assert length(activities) == 2
2643     end
2644   end
2645
2646   test "allow fetching of accounts with an empty string name field" do
2647     Tesla.Mock.mock(fn
2648       %{method: :get, url: "https://princess.cat/users/mewmew"} ->
2649         file = File.read!("test/fixtures/mewmew_no_name.json")
2650         %Tesla.Env{status: 200, body: file, headers: HttpRequestMock.activitypub_object_headers()}
2651     end)
2652
2653     {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew")
2654     assert user.name == " "
2655   end
2656 end