8c9c2c89ec013042205836c090a6955c764d03eb
[akkoma] / test / web / activity_pub / activity_pub_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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 alias Pleroma.Activity
8 alias Pleroma.Builders.ActivityBuilder
9 alias Pleroma.Object
10 alias Pleroma.User
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.Utils
13 alias Pleroma.Web.CommonAPI
14
15 import Pleroma.Factory
16 import Tesla.Mock
17 import Mock
18
19 setup do
20 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
21 :ok
22 end
23
24 clear_config([:instance, :federating])
25
26 describe "streaming out participations" do
27 test "it streams them out" do
28 user = insert(:user)
29 {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
30
31 {:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity)
32
33 participations =
34 conversation.participations
35 |> Repo.preload(:user)
36
37 with_mock Pleroma.Web.Streamer,
38 stream: fn _, _ -> nil end do
39 ActivityPub.stream_out_participations(conversation.participations)
40
41 assert called(Pleroma.Web.Streamer.stream("participation", participations))
42 end
43 end
44 end
45
46 describe "fetching restricted by visibility" do
47 test "it restricts by the appropriate visibility" do
48 user = insert(:user)
49
50 {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
51
52 {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
53
54 {:ok, unlisted_activity} =
55 CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
56
57 {:ok, private_activity} =
58 CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
59
60 activities =
61 ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id})
62
63 assert activities == [direct_activity]
64
65 activities =
66 ActivityPub.fetch_activities([], %{:visibility => "unlisted", "actor_id" => user.ap_id})
67
68 assert activities == [unlisted_activity]
69
70 activities =
71 ActivityPub.fetch_activities([], %{:visibility => "private", "actor_id" => user.ap_id})
72
73 assert activities == [private_activity]
74
75 activities =
76 ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id})
77
78 assert activities == [public_activity]
79
80 activities =
81 ActivityPub.fetch_activities([], %{
82 :visibility => ~w[private public],
83 "actor_id" => user.ap_id
84 })
85
86 assert activities == [public_activity, private_activity]
87 end
88 end
89
90 describe "building a user from his ap id" do
91 test "it returns a user" do
92 user_id = "http://mastodon.example.org/users/admin"
93 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
94 assert user.ap_id == user_id
95 assert user.nickname == "admin@mastodon.example.org"
96 assert user.info.source_data
97 assert user.info.ap_enabled
98 assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
99 end
100
101 test "it fetches the appropriate tag-restricted posts" do
102 user = insert(:user)
103
104 {:ok, status_one} = CommonAPI.post(user, %{"status" => ". #test"})
105 {:ok, status_two} = CommonAPI.post(user, %{"status" => ". #essais"})
106 {:ok, status_three} = CommonAPI.post(user, %{"status" => ". #test #reject"})
107
108 fetch_one = ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => "test"})
109
110 fetch_two =
111 ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => ["test", "essais"]})
112
113 fetch_three =
114 ActivityPub.fetch_activities([], %{
115 "type" => "Create",
116 "tag" => ["test", "essais"],
117 "tag_reject" => ["reject"]
118 })
119
120 fetch_four =
121 ActivityPub.fetch_activities([], %{
122 "type" => "Create",
123 "tag" => ["test"],
124 "tag_all" => ["test", "reject"]
125 })
126
127 assert fetch_one == [status_one, status_three]
128 assert fetch_two == [status_one, status_two, status_three]
129 assert fetch_three == [status_one, status_two]
130 assert fetch_four == [status_three]
131 end
132 end
133
134 describe "insertion" do
135 test "drops activities beyond a certain limit" do
136 limit = Pleroma.Config.get([:instance, :remote_limit])
137
138 random_text =
139 :crypto.strong_rand_bytes(limit + 1)
140 |> Base.encode64()
141 |> binary_part(0, limit + 1)
142
143 data = %{
144 "ok" => true,
145 "object" => %{
146 "content" => random_text
147 }
148 }
149
150 assert {:error, {:remote_limit_error, _}} = ActivityPub.insert(data)
151 end
152
153 test "doesn't drop activities with content being null" do
154 user = insert(:user)
155
156 data = %{
157 "actor" => user.ap_id,
158 "to" => [],
159 "object" => %{
160 "actor" => user.ap_id,
161 "to" => [],
162 "type" => "Note",
163 "content" => nil
164 }
165 }
166
167 assert {:ok, _} = ActivityPub.insert(data)
168 end
169
170 test "returns the activity if one with the same id is already in" do
171 activity = insert(:note_activity)
172 {:ok, new_activity} = ActivityPub.insert(activity.data)
173
174 assert activity.id == new_activity.id
175 end
176
177 test "inserts a given map into the activity database, giving it an id if it has none." do
178 user = insert(:user)
179
180 data = %{
181 "actor" => user.ap_id,
182 "to" => [],
183 "object" => %{
184 "actor" => user.ap_id,
185 "to" => [],
186 "type" => "Note",
187 "content" => "hey"
188 }
189 }
190
191 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
192 assert activity.data["ok"] == data["ok"]
193 assert is_binary(activity.data["id"])
194
195 given_id = "bla"
196
197 data = %{
198 "id" => given_id,
199 "actor" => user.ap_id,
200 "to" => [],
201 "context" => "blabla",
202 "object" => %{
203 "actor" => user.ap_id,
204 "to" => [],
205 "type" => "Note",
206 "content" => "hey"
207 }
208 }
209
210 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
211 assert activity.data["ok"] == data["ok"]
212 assert activity.data["id"] == given_id
213 assert activity.data["context"] == "blabla"
214 assert activity.data["context_id"]
215 end
216
217 test "adds a context when none is there" do
218 user = insert(:user)
219
220 data = %{
221 "actor" => user.ap_id,
222 "to" => [],
223 "object" => %{
224 "actor" => user.ap_id,
225 "to" => [],
226 "type" => "Note",
227 "content" => "hey"
228 }
229 }
230
231 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
232 object = Pleroma.Object.normalize(activity)
233
234 assert is_binary(activity.data["context"])
235 assert is_binary(object.data["context"])
236 assert activity.data["context_id"]
237 assert object.data["context_id"]
238 end
239
240 test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
241 user = insert(:user)
242
243 data = %{
244 "actor" => user.ap_id,
245 "to" => [],
246 "object" => %{
247 "actor" => user.ap_id,
248 "to" => [],
249 "type" => "Note",
250 "content" => "hey"
251 }
252 }
253
254 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
255 assert object = Object.normalize(activity)
256 assert is_binary(object.data["id"])
257 end
258 end
259
260 describe "listen activities" do
261 test "does not increase user note count" do
262 user = insert(:user)
263
264 {:ok, activity} =
265 ActivityPub.listen(%{
266 to: ["https://www.w3.org/ns/activitystreams#Public"],
267 actor: user,
268 context: "",
269 object: %{
270 "actor" => user.ap_id,
271 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
272 "artist" => "lain",
273 "title" => "lain radio episode 1",
274 "length" => 180_000,
275 "type" => "Audio"
276 }
277 })
278
279 assert activity.actor == user.ap_id
280
281 user = User.get_cached_by_id(user.id)
282 assert user.info.note_count == 0
283 end
284
285 test "can be fetched into a timeline" do
286 _listen_activity_1 = insert(:listen)
287 _listen_activity_2 = insert(:listen)
288 _listen_activity_3 = insert(:listen)
289
290 timeline = ActivityPub.fetch_activities([], %{"type" => ["Listen"]})
291
292 assert length(timeline) == 3
293 end
294 end
295
296 describe "create activities" do
297 test "removes doubled 'to' recipients" do
298 user = insert(:user)
299
300 {:ok, activity} =
301 ActivityPub.create(%{
302 to: ["user1", "user1", "user2"],
303 actor: user,
304 context: "",
305 object: %{
306 "to" => ["user1", "user1", "user2"],
307 "type" => "Note",
308 "content" => "testing"
309 }
310 })
311
312 assert activity.data["to"] == ["user1", "user2"]
313 assert activity.actor == user.ap_id
314 assert activity.recipients == ["user1", "user2", user.ap_id]
315 end
316
317 test "increases user note count only for public activities" do
318 user = insert(:user)
319
320 {:ok, _} =
321 CommonAPI.post(User.get_cached_by_id(user.id), %{
322 "status" => "1",
323 "visibility" => "public"
324 })
325
326 {:ok, _} =
327 CommonAPI.post(User.get_cached_by_id(user.id), %{
328 "status" => "2",
329 "visibility" => "unlisted"
330 })
331
332 {:ok, _} =
333 CommonAPI.post(User.get_cached_by_id(user.id), %{
334 "status" => "2",
335 "visibility" => "private"
336 })
337
338 {:ok, _} =
339 CommonAPI.post(User.get_cached_by_id(user.id), %{
340 "status" => "3",
341 "visibility" => "direct"
342 })
343
344 user = User.get_cached_by_id(user.id)
345 assert user.info.note_count == 2
346 end
347
348 test "increases replies count" do
349 user = insert(:user)
350 user2 = insert(:user)
351
352 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
353 ap_id = activity.data["id"]
354 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
355
356 # public
357 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
358 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
359 assert object.data["repliesCount"] == 1
360
361 # unlisted
362 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
363 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
364 assert object.data["repliesCount"] == 2
365
366 # private
367 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
368 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
369 assert object.data["repliesCount"] == 2
370
371 # direct
372 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
373 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
374 assert object.data["repliesCount"] == 2
375 end
376 end
377
378 describe "fetch activities for recipients" do
379 test "retrieve the activities for certain recipients" do
380 {:ok, activity_one} = ActivityBuilder.insert(%{"to" => ["someone"]})
381 {:ok, activity_two} = ActivityBuilder.insert(%{"to" => ["someone_else"]})
382 {:ok, _activity_three} = ActivityBuilder.insert(%{"to" => ["noone"]})
383
384 activities = ActivityPub.fetch_activities(["someone", "someone_else"])
385 assert length(activities) == 2
386 assert activities == [activity_one, activity_two]
387 end
388 end
389
390 describe "fetch activities in context" do
391 test "retrieves activities that have a given context" do
392 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
393 {:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
394 {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
395 {:ok, _activity_four} = ActivityBuilder.insert(%{"type" => "Announce", "context" => "2hu"})
396 activity_five = insert(:note_activity)
397 user = insert(:user)
398
399 {:ok, user} = User.block(user, %{ap_id: activity_five.data["actor"]})
400
401 activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})
402 assert activities == [activity_two, activity]
403 end
404 end
405
406 test "doesn't return blocked activities" do
407 activity_one = insert(:note_activity)
408 activity_two = insert(:note_activity)
409 activity_three = insert(:note_activity)
410 user = insert(:user)
411 booster = insert(:user)
412 {:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]})
413
414 activities =
415 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
416
417 assert Enum.member?(activities, activity_two)
418 assert Enum.member?(activities, activity_three)
419 refute Enum.member?(activities, activity_one)
420
421 {:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
422
423 activities =
424 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
425
426 assert Enum.member?(activities, activity_two)
427 assert Enum.member?(activities, activity_three)
428 assert Enum.member?(activities, activity_one)
429
430 {:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})
431 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
432 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
433 activity_three = Activity.get_by_id(activity_three.id)
434
435 activities =
436 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
437
438 assert Enum.member?(activities, activity_two)
439 refute Enum.member?(activities, activity_three)
440 refute Enum.member?(activities, boost_activity)
441 assert Enum.member?(activities, activity_one)
442
443 activities =
444 ActivityPub.fetch_activities([], %{"blocking_user" => nil, "skip_preload" => true})
445
446 assert Enum.member?(activities, activity_two)
447 assert Enum.member?(activities, activity_three)
448 assert Enum.member?(activities, boost_activity)
449 assert Enum.member?(activities, activity_one)
450 end
451
452 test "doesn't return transitive interactions concerning blocked users" do
453 blocker = insert(:user)
454 blockee = insert(:user)
455 friend = insert(:user)
456
457 {:ok, blocker} = User.block(blocker, blockee)
458
459 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
460
461 {:ok, activity_two} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"})
462
463 {:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
464
465 {:ok, activity_four} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"})
466
467 activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
468
469 assert Enum.member?(activities, activity_one)
470 refute Enum.member?(activities, activity_two)
471 refute Enum.member?(activities, activity_three)
472 refute Enum.member?(activities, activity_four)
473 end
474
475 test "doesn't return announce activities concerning blocked users" do
476 blocker = insert(:user)
477 blockee = insert(:user)
478 friend = insert(:user)
479
480 {:ok, blocker} = User.block(blocker, blockee)
481
482 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
483
484 {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
485
486 {:ok, activity_three, _} = CommonAPI.repeat(activity_two.id, friend)
487
488 activities =
489 ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
490 |> Enum.map(fn act -> act.id end)
491
492 assert Enum.member?(activities, activity_one.id)
493 refute Enum.member?(activities, activity_two.id)
494 refute Enum.member?(activities, activity_three.id)
495 end
496
497 test "doesn't return activities from blocked domains" do
498 domain = "dogwhistle.zone"
499 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
500 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
501 activity = insert(:note_activity, %{note: note})
502 user = insert(:user)
503 {:ok, user} = User.block_domain(user, domain)
504
505 activities =
506 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
507
508 refute activity in activities
509
510 followed_user = insert(:user)
511 ActivityPub.follow(user, followed_user)
512 {:ok, repeat_activity, _} = CommonAPI.repeat(activity.id, followed_user)
513
514 activities =
515 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
516
517 refute repeat_activity in activities
518 end
519
520 test "doesn't return muted activities" do
521 activity_one = insert(:note_activity)
522 activity_two = insert(:note_activity)
523 activity_three = insert(:note_activity)
524 user = insert(:user)
525 booster = insert(:user)
526 {:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]})
527
528 activities =
529 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
530
531 assert Enum.member?(activities, activity_two)
532 assert Enum.member?(activities, activity_three)
533 refute Enum.member?(activities, activity_one)
534
535 # Calling with 'with_muted' will deliver muted activities, too.
536 activities =
537 ActivityPub.fetch_activities([], %{
538 "muting_user" => user,
539 "with_muted" => true,
540 "skip_preload" => true
541 })
542
543 assert Enum.member?(activities, activity_two)
544 assert Enum.member?(activities, activity_three)
545 assert Enum.member?(activities, activity_one)
546
547 {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]})
548
549 activities =
550 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
551
552 assert Enum.member?(activities, activity_two)
553 assert Enum.member?(activities, activity_three)
554 assert Enum.member?(activities, activity_one)
555
556 {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
557 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
558 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
559 activity_three = Activity.get_by_id(activity_three.id)
560
561 activities =
562 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
563
564 assert Enum.member?(activities, activity_two)
565 refute Enum.member?(activities, activity_three)
566 refute Enum.member?(activities, boost_activity)
567 assert Enum.member?(activities, activity_one)
568
569 activities = ActivityPub.fetch_activities([], %{"muting_user" => nil, "skip_preload" => true})
570
571 assert Enum.member?(activities, activity_two)
572 assert Enum.member?(activities, activity_three)
573 assert Enum.member?(activities, boost_activity)
574 assert Enum.member?(activities, activity_one)
575 end
576
577 test "doesn't return thread muted activities" do
578 user = insert(:user)
579 _activity_one = insert(:note_activity)
580 note_two = insert(:note, data: %{"context" => "suya.."})
581 activity_two = insert(:note_activity, note: note_two)
582
583 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
584
585 assert [_activity_one] = ActivityPub.fetch_activities([], %{"muting_user" => user})
586 end
587
588 test "returns thread muted activities when with_muted is set" do
589 user = insert(:user)
590 _activity_one = insert(:note_activity)
591 note_two = insert(:note, data: %{"context" => "suya.."})
592 activity_two = insert(:note_activity, note: note_two)
593
594 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
595
596 assert [_activity_two, _activity_one] =
597 ActivityPub.fetch_activities([], %{"muting_user" => user, "with_muted" => true})
598 end
599
600 test "does include announces on request" do
601 activity_three = insert(:note_activity)
602 user = insert(:user)
603 booster = insert(:user)
604
605 {:ok, user} = User.follow(user, booster)
606
607 {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster)
608
609 [announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following])
610
611 assert announce_activity.id == announce.id
612 end
613
614 test "excludes reblogs on request" do
615 user = insert(:user)
616 {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
617 {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
618
619 [activity] = ActivityPub.fetch_user_activities(user, nil, %{"exclude_reblogs" => "true"})
620
621 assert activity == expected_activity
622 end
623
624 describe "public fetch activities" do
625 test "doesn't retrieve unlisted activities" do
626 user = insert(:user)
627
628 {:ok, _unlisted_activity} =
629 CommonAPI.post(user, %{"status" => "yeah", "visibility" => "unlisted"})
630
631 {:ok, listed_activity} = CommonAPI.post(user, %{"status" => "yeah"})
632
633 [activity] = ActivityPub.fetch_public_activities()
634
635 assert activity == listed_activity
636 end
637
638 test "retrieves public activities" do
639 _activities = ActivityPub.fetch_public_activities()
640
641 %{public: public} = ActivityBuilder.public_and_non_public()
642
643 activities = ActivityPub.fetch_public_activities()
644 assert length(activities) == 1
645 assert Enum.at(activities, 0) == public
646 end
647
648 test "retrieves a maximum of 20 activities" do
649 activities = ActivityBuilder.insert_list(30)
650 last_expected = List.last(activities)
651
652 activities = ActivityPub.fetch_public_activities()
653 last = List.last(activities)
654
655 assert length(activities) == 20
656 assert last == last_expected
657 end
658
659 test "retrieves ids starting from a since_id" do
660 activities = ActivityBuilder.insert_list(30)
661 later_activities = ActivityBuilder.insert_list(10)
662 since_id = List.last(activities).id
663 last_expected = List.last(later_activities)
664
665 activities = ActivityPub.fetch_public_activities(%{"since_id" => since_id})
666 last = List.last(activities)
667
668 assert length(activities) == 10
669 assert last == last_expected
670 end
671
672 test "retrieves ids up to max_id" do
673 _first_activities = ActivityBuilder.insert_list(10)
674 activities = ActivityBuilder.insert_list(20)
675 later_activities = ActivityBuilder.insert_list(10)
676 max_id = List.first(later_activities).id
677 last_expected = List.last(activities)
678
679 activities = ActivityPub.fetch_public_activities(%{"max_id" => max_id})
680 last = List.last(activities)
681
682 assert length(activities) == 20
683 assert last == last_expected
684 end
685
686 test "paginates via offset/limit" do
687 _first_activities = ActivityBuilder.insert_list(10)
688 activities = ActivityBuilder.insert_list(10)
689 _later_activities = ActivityBuilder.insert_list(10)
690 first_expected = List.first(activities)
691
692 activities =
693 ActivityPub.fetch_public_activities(%{"page" => "2", "page_size" => "20"}, :offset)
694
695 first = List.first(activities)
696
697 assert length(activities) == 20
698 assert first == first_expected
699 end
700
701 test "doesn't return reblogs for users for whom reblogs have been muted" do
702 activity = insert(:note_activity)
703 user = insert(:user)
704 booster = insert(:user)
705 {:ok, user} = CommonAPI.hide_reblogs(user, booster)
706
707 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
708
709 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
710
711 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
712 end
713
714 test "returns reblogs for users for whom reblogs have not been muted" do
715 activity = insert(:note_activity)
716 user = insert(:user)
717 booster = insert(:user)
718 {:ok, user} = CommonAPI.hide_reblogs(user, booster)
719 {:ok, user} = CommonAPI.show_reblogs(user, booster)
720
721 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
722
723 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
724
725 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
726 end
727 end
728
729 describe "react to an object" do
730 test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do
731 Pleroma.Config.put([:instance, :federating], true)
732 user = insert(:user)
733 reactor = insert(:user)
734 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
735 assert object = Object.normalize(activity)
736
737 {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
738
739 assert called(Pleroma.Web.Federator.publish(reaction_activity))
740 end
741
742 test "adds an emoji reaction activity to the db" do
743 user = insert(:user)
744 reactor = insert(:user)
745 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
746 assert object = Object.normalize(activity)
747
748 {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
749
750 assert reaction_activity
751
752 assert reaction_activity.data["actor"] == reactor.ap_id
753 assert reaction_activity.data["type"] == "EmojiReaction"
754 assert reaction_activity.data["content"] == "🔥"
755 assert reaction_activity.data["object"] == object.data["id"]
756 assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]]
757 assert reaction_activity.data["context"] == object.data["context"]
758 assert object.data["reaction_count"] == 1
759 assert object.data["reactions"]["🔥"] == [reactor.ap_id]
760 end
761 end
762
763 describe "unreacting to an object" do
764 test "adds an emoji reaction activity to the db" do
765 user = insert(:user)
766 reactor = insert(:user)
767 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
768 assert object = Object.normalize(activity)
769
770 {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
771 {:ok, unreaction_activity} = ActivityPub.unreact_with_emoji(reactor, reaction_activity.id)
772
773 IO.inspect(object)
774 end
775 end
776
777 describe "like an object" do
778 test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do
779 Pleroma.Config.put([:instance, :federating], true)
780 note_activity = insert(:note_activity)
781 assert object_activity = Object.normalize(note_activity)
782
783 user = insert(:user)
784
785 {:ok, like_activity, _object} = ActivityPub.like(user, object_activity)
786 assert called(Pleroma.Web.Federator.publish(like_activity))
787 end
788
789 test "returns exist activity if object already liked" do
790 note_activity = insert(:note_activity)
791 assert object_activity = Object.normalize(note_activity)
792
793 user = insert(:user)
794
795 {:ok, like_activity, _object} = ActivityPub.like(user, object_activity)
796
797 {:ok, like_activity_exist, _object} = ActivityPub.like(user, object_activity)
798 assert like_activity == like_activity_exist
799 end
800
801 test "adds a like activity to the db" do
802 note_activity = insert(:note_activity)
803 assert object = Object.normalize(note_activity)
804
805 user = insert(:user)
806 user_two = insert(:user)
807
808 {:ok, like_activity, object} = ActivityPub.like(user, object)
809
810 assert like_activity.data["actor"] == user.ap_id
811 assert like_activity.data["type"] == "Like"
812 assert like_activity.data["object"] == object.data["id"]
813 assert like_activity.data["to"] == [User.ap_followers(user), note_activity.data["actor"]]
814 assert like_activity.data["context"] == object.data["context"]
815 assert object.data["like_count"] == 1
816 assert object.data["likes"] == [user.ap_id]
817
818 # Just return the original activity if the user already liked it.
819 {:ok, same_like_activity, object} = ActivityPub.like(user, object)
820
821 assert like_activity == same_like_activity
822 assert object.data["likes"] == [user.ap_id]
823 assert object.data["like_count"] == 1
824
825 {:ok, _like_activity, object} = ActivityPub.like(user_two, object)
826 assert object.data["like_count"] == 2
827 end
828 end
829
830 describe "unliking" do
831 test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do
832 Pleroma.Config.put([:instance, :federating], true)
833
834 note_activity = insert(:note_activity)
835 object = Object.normalize(note_activity)
836 user = insert(:user)
837
838 {:ok, object} = ActivityPub.unlike(user, object)
839 refute called(Pleroma.Web.Federator.publish())
840
841 {:ok, _like_activity, object} = ActivityPub.like(user, object)
842 assert object.data["like_count"] == 1
843
844 {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
845 assert object.data["like_count"] == 0
846
847 assert called(Pleroma.Web.Federator.publish(unlike_activity))
848 end
849
850 test "unliking a previously liked object" do
851 note_activity = insert(:note_activity)
852 object = Object.normalize(note_activity)
853 user = insert(:user)
854
855 # Unliking something that hasn't been liked does nothing
856 {:ok, object} = ActivityPub.unlike(user, object)
857 assert object.data["like_count"] == 0
858
859 {:ok, like_activity, object} = ActivityPub.like(user, object)
860 assert object.data["like_count"] == 1
861
862 {:ok, _, _, object} = ActivityPub.unlike(user, object)
863 assert object.data["like_count"] == 0
864
865 assert Activity.get_by_id(like_activity.id) == nil
866 end
867 end
868
869 describe "announcing an object" do
870 test "adds an announce activity to the db" do
871 note_activity = insert(:note_activity)
872 object = Object.normalize(note_activity)
873 user = insert(:user)
874
875 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
876 assert object.data["announcement_count"] == 1
877 assert object.data["announcements"] == [user.ap_id]
878
879 assert announce_activity.data["to"] == [
880 User.ap_followers(user),
881 note_activity.data["actor"]
882 ]
883
884 assert announce_activity.data["object"] == object.data["id"]
885 assert announce_activity.data["actor"] == user.ap_id
886 assert announce_activity.data["context"] == object.data["context"]
887 end
888 end
889
890 describe "announcing a private object" do
891 test "adds an announce activity to the db if the audience is not widened" do
892 user = insert(:user)
893 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
894 object = Object.normalize(note_activity)
895
896 {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false)
897
898 assert announce_activity.data["to"] == [User.ap_followers(user)]
899
900 assert announce_activity.data["object"] == object.data["id"]
901 assert announce_activity.data["actor"] == user.ap_id
902 assert announce_activity.data["context"] == object.data["context"]
903 end
904
905 test "does not add an announce activity to the db if the audience is widened" do
906 user = insert(:user)
907 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
908 object = Object.normalize(note_activity)
909
910 assert {:error, _} = ActivityPub.announce(user, object, nil, true, true)
911 end
912
913 test "does not add an announce activity to the db if the announcer is not the author" do
914 user = insert(:user)
915 announcer = insert(:user)
916 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
917 object = Object.normalize(note_activity)
918
919 assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false)
920 end
921 end
922
923 describe "unannouncing an object" do
924 test "unannouncing a previously announced object" do
925 note_activity = insert(:note_activity)
926 object = Object.normalize(note_activity)
927 user = insert(:user)
928
929 # Unannouncing an object that is not announced does nothing
930 # {:ok, object} = ActivityPub.unannounce(user, object)
931 # assert object.data["announcement_count"] == 0
932
933 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
934 assert object.data["announcement_count"] == 1
935
936 {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
937 assert object.data["announcement_count"] == 0
938
939 assert unannounce_activity.data["to"] == [
940 User.ap_followers(user),
941 announce_activity.data["actor"]
942 ]
943
944 assert unannounce_activity.data["type"] == "Undo"
945 assert unannounce_activity.data["object"] == announce_activity.data
946 assert unannounce_activity.data["actor"] == user.ap_id
947 assert unannounce_activity.data["context"] == announce_activity.data["context"]
948
949 assert Activity.get_by_id(announce_activity.id) == nil
950 end
951 end
952
953 describe "uploading files" do
954 test "copies the file to the configured folder" do
955 file = %Plug.Upload{
956 content_type: "image/jpg",
957 path: Path.absname("test/fixtures/image.jpg"),
958 filename: "an_image.jpg"
959 }
960
961 {:ok, %Object{} = object} = ActivityPub.upload(file)
962 assert object.data["name"] == "an_image.jpg"
963 end
964
965 test "works with base64 encoded images" do
966 file = %{
967 "img" => data_uri()
968 }
969
970 {:ok, %Object{}} = ActivityPub.upload(file)
971 end
972 end
973
974 describe "fetch the latest Follow" do
975 test "fetches the latest Follow activity" do
976 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
977 follower = Repo.get_by(User, ap_id: activity.data["actor"])
978 followed = Repo.get_by(User, ap_id: activity.data["object"])
979
980 assert activity == Utils.fetch_latest_follow(follower, followed)
981 end
982 end
983
984 describe "following / unfollowing" do
985 test "creates a follow activity" do
986 follower = insert(:user)
987 followed = insert(:user)
988
989 {:ok, activity} = ActivityPub.follow(follower, followed)
990 assert activity.data["type"] == "Follow"
991 assert activity.data["actor"] == follower.ap_id
992 assert activity.data["object"] == followed.ap_id
993 end
994
995 test "creates an undo activity for the last follow" do
996 follower = insert(:user)
997 followed = insert(:user)
998
999 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1000 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1001
1002 assert activity.data["type"] == "Undo"
1003 assert activity.data["actor"] == follower.ap_id
1004
1005 embedded_object = activity.data["object"]
1006 assert is_map(embedded_object)
1007 assert embedded_object["type"] == "Follow"
1008 assert embedded_object["object"] == followed.ap_id
1009 assert embedded_object["id"] == follow_activity.data["id"]
1010 end
1011 end
1012
1013 describe "blocking / unblocking" do
1014 test "creates a block activity" do
1015 blocker = insert(:user)
1016 blocked = insert(:user)
1017
1018 {:ok, activity} = ActivityPub.block(blocker, blocked)
1019
1020 assert activity.data["type"] == "Block"
1021 assert activity.data["actor"] == blocker.ap_id
1022 assert activity.data["object"] == blocked.ap_id
1023 end
1024
1025 test "creates an undo activity for the last block" do
1026 blocker = insert(:user)
1027 blocked = insert(:user)
1028
1029 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1030 {:ok, activity} = ActivityPub.unblock(blocker, blocked)
1031
1032 assert activity.data["type"] == "Undo"
1033 assert activity.data["actor"] == blocker.ap_id
1034
1035 embedded_object = activity.data["object"]
1036 assert is_map(embedded_object)
1037 assert embedded_object["type"] == "Block"
1038 assert embedded_object["object"] == blocked.ap_id
1039 assert embedded_object["id"] == block_activity.data["id"]
1040 end
1041 end
1042
1043 describe "deletion" do
1044 test "it creates a delete activity and deletes the original object" do
1045 note = insert(:note_activity)
1046 object = Object.normalize(note)
1047 {:ok, delete} = ActivityPub.delete(object)
1048
1049 assert delete.data["type"] == "Delete"
1050 assert delete.data["actor"] == note.data["actor"]
1051 assert delete.data["object"] == object.data["id"]
1052
1053 assert Activity.get_by_id(delete.id) != nil
1054
1055 assert Repo.get(Object, object.id).data["type"] == "Tombstone"
1056 end
1057
1058 test "decrements user note count only for public activities" do
1059 user = insert(:user, info: %{note_count: 10})
1060
1061 {:ok, a1} =
1062 CommonAPI.post(User.get_cached_by_id(user.id), %{
1063 "status" => "yeah",
1064 "visibility" => "public"
1065 })
1066
1067 {:ok, a2} =
1068 CommonAPI.post(User.get_cached_by_id(user.id), %{
1069 "status" => "yeah",
1070 "visibility" => "unlisted"
1071 })
1072
1073 {:ok, a3} =
1074 CommonAPI.post(User.get_cached_by_id(user.id), %{
1075 "status" => "yeah",
1076 "visibility" => "private"
1077 })
1078
1079 {:ok, a4} =
1080 CommonAPI.post(User.get_cached_by_id(user.id), %{
1081 "status" => "yeah",
1082 "visibility" => "direct"
1083 })
1084
1085 {:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
1086 {:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
1087 {:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
1088 {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
1089
1090 user = User.get_cached_by_id(user.id)
1091 assert user.info.note_count == 10
1092 end
1093
1094 test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
1095 user = insert(:user)
1096 note = insert(:note_activity)
1097 object = Object.normalize(note)
1098
1099 {:ok, object} =
1100 object
1101 |> Object.change(%{
1102 data: %{
1103 "actor" => object.data["actor"],
1104 "id" => object.data["id"],
1105 "to" => [user.ap_id],
1106 "type" => "Note"
1107 }
1108 })
1109 |> Object.update_and_set_cache()
1110
1111 {:ok, delete} = ActivityPub.delete(object)
1112
1113 assert user.ap_id in delete.data["to"]
1114 end
1115
1116 test "decreases reply count" do
1117 user = insert(:user)
1118 user2 = insert(:user)
1119
1120 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
1121 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
1122 ap_id = activity.data["id"]
1123
1124 {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
1125 {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
1126 {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
1127 {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
1128
1129 _ = CommonAPI.delete(direct_reply.id, user2)
1130 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1131 assert object.data["repliesCount"] == 2
1132
1133 _ = CommonAPI.delete(private_reply.id, user2)
1134 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1135 assert object.data["repliesCount"] == 2
1136
1137 _ = CommonAPI.delete(public_reply.id, user2)
1138 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1139 assert object.data["repliesCount"] == 1
1140
1141 _ = CommonAPI.delete(unlisted_reply.id, user2)
1142 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1143 assert object.data["repliesCount"] == 0
1144 end
1145 end
1146
1147 describe "timeline post-processing" do
1148 test "it filters broken threads" do
1149 user1 = insert(:user)
1150 user2 = insert(:user)
1151 user3 = insert(:user)
1152
1153 {:ok, user1} = User.follow(user1, user3)
1154 assert User.following?(user1, user3)
1155
1156 {:ok, user2} = User.follow(user2, user3)
1157 assert User.following?(user2, user3)
1158
1159 {:ok, user3} = User.follow(user3, user2)
1160 assert User.following?(user3, user2)
1161
1162 {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"})
1163
1164 {:ok, private_activity_1} =
1165 CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
1166
1167 {:ok, private_activity_2} =
1168 CommonAPI.post(user2, %{
1169 "status" => "hi 3",
1170 "visibility" => "private",
1171 "in_reply_to_status_id" => private_activity_1.id
1172 })
1173
1174 {:ok, private_activity_3} =
1175 CommonAPI.post(user3, %{
1176 "status" => "hi 4",
1177 "visibility" => "private",
1178 "in_reply_to_status_id" => private_activity_2.id
1179 })
1180
1181 activities =
1182 ActivityPub.fetch_activities([user1.ap_id | user1.following])
1183 |> Enum.map(fn a -> a.id end)
1184
1185 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1186
1187 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1188
1189 assert length(activities) == 3
1190
1191 activities =
1192 ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
1193 |> Enum.map(fn a -> a.id end)
1194
1195 assert [public_activity.id, private_activity_1.id] == activities
1196 assert length(activities) == 2
1197 end
1198 end
1199
1200 describe "update" do
1201 test "it creates an update activity with the new user data" do
1202 user = insert(:user)
1203 {:ok, user} = User.ensure_keys_present(user)
1204 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
1205
1206 {:ok, update} =
1207 ActivityPub.update(%{
1208 actor: user_data["id"],
1209 to: [user.follower_address],
1210 cc: [],
1211 object: user_data
1212 })
1213
1214 assert update.data["actor"] == user.ap_id
1215 assert update.data["to"] == [user.follower_address]
1216 assert embedded_object = update.data["object"]
1217 assert embedded_object["id"] == user_data["id"]
1218 assert embedded_object["type"] == user_data["type"]
1219 end
1220 end
1221
1222 test "returned pinned statuses" do
1223 Pleroma.Config.put([:instance, :max_pinned_statuses], 3)
1224 user = insert(:user)
1225
1226 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
1227 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
1228 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
1229
1230 CommonAPI.pin(activity_one.id, user)
1231 user = refresh_record(user)
1232
1233 CommonAPI.pin(activity_two.id, user)
1234 user = refresh_record(user)
1235
1236 CommonAPI.pin(activity_three.id, user)
1237 user = refresh_record(user)
1238
1239 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
1240
1241 assert 3 = length(activities)
1242 end
1243
1244 test "it can create a Flag activity" do
1245 reporter = insert(:user)
1246 target_account = insert(:user)
1247 {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"})
1248 context = Utils.generate_context_id()
1249 content = "foobar"
1250
1251 reporter_ap_id = reporter.ap_id
1252 target_ap_id = target_account.ap_id
1253 activity_ap_id = activity.data["id"]
1254
1255 assert {:ok, activity} =
1256 ActivityPub.flag(%{
1257 actor: reporter,
1258 context: context,
1259 account: target_account,
1260 statuses: [activity],
1261 content: content
1262 })
1263
1264 assert %Activity{
1265 actor: ^reporter_ap_id,
1266 data: %{
1267 "type" => "Flag",
1268 "content" => ^content,
1269 "context" => ^context,
1270 "object" => [^target_ap_id, ^activity_ap_id]
1271 }
1272 } = activity
1273 end
1274
1275 test "fetch_activities/2 returns activities addressed to a list " do
1276 user = insert(:user)
1277 member = insert(:user)
1278 {:ok, list} = Pleroma.List.create("foo", user)
1279 {:ok, list} = Pleroma.List.follow(list, member)
1280
1281 {:ok, activity} =
1282 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1283
1284 activity = Repo.preload(activity, :bookmark)
1285 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1286
1287 assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
1288 end
1289
1290 def data_uri do
1291 File.read!("test/fixtures/avatar_data_uri")
1292 end
1293
1294 describe "fetch_activities_bounded" do
1295 test "fetches private posts for followed users" do
1296 user = insert(:user)
1297
1298 {:ok, activity} =
1299 CommonAPI.post(user, %{
1300 "status" => "thought I looked cute might delete later :3",
1301 "visibility" => "private"
1302 })
1303
1304 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1305 assert result.id == activity.id
1306 end
1307
1308 test "fetches only public posts for other users" do
1309 user = insert(:user)
1310 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
1311
1312 {:ok, _private_activity} =
1313 CommonAPI.post(user, %{
1314 "status" => "why is tenshi eating a corndog so cute?",
1315 "visibility" => "private"
1316 })
1317
1318 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1319 assert result.id == activity.id
1320 end
1321 end
1322
1323 describe "fetch_follow_information_for_user" do
1324 test "syncronizes following/followers counters" do
1325 user =
1326 insert(:user,
1327 local: false,
1328 follower_address: "http://localhost:4001/users/fuser2/followers",
1329 following_address: "http://localhost:4001/users/fuser2/following"
1330 )
1331
1332 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1333 assert info.follower_count == 527
1334 assert info.following_count == 267
1335 end
1336
1337 test "detects hidden followers" do
1338 mock(fn env ->
1339 case env.url do
1340 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1341 %Tesla.Env{status: 403, body: ""}
1342
1343 _ ->
1344 apply(HttpRequestMock, :request, [env])
1345 end
1346 end)
1347
1348 user =
1349 insert(:user,
1350 local: false,
1351 follower_address: "http://localhost:4001/users/masto_closed/followers",
1352 following_address: "http://localhost:4001/users/masto_closed/following"
1353 )
1354
1355 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1356 assert info.hide_followers == true
1357 assert info.hide_follows == false
1358 end
1359
1360 test "detects hidden follows" do
1361 mock(fn env ->
1362 case env.url do
1363 "http://localhost:4001/users/masto_closed/following?page=1" ->
1364 %Tesla.Env{status: 403, body: ""}
1365
1366 _ ->
1367 apply(HttpRequestMock, :request, [env])
1368 end
1369 end)
1370
1371 user =
1372 insert(:user,
1373 local: false,
1374 follower_address: "http://localhost:4001/users/masto_closed/followers",
1375 following_address: "http://localhost:4001/users/masto_closed/following"
1376 )
1377
1378 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1379 assert info.hide_followers == false
1380 assert info.hide_follows == true
1381 end
1382 end
1383 end