ActivityPub: Fix non-federating blocks.
[akkoma] / test / web / activity_pub / activity_pub_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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 alias Pleroma.Web.Federator
20
21 import Pleroma.Factory
22 import Tesla.Mock
23 import Mock
24
25 setup do
26 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
27 :ok
28 end
29
30 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} =
82 CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
83
84 {:ok, private_activity} =
85 CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
86
87 activities =
88 ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id})
89
90 assert activities == [direct_activity]
91
92 activities =
93 ActivityPub.fetch_activities([], %{:visibility => "unlisted", "actor_id" => user.ap_id})
94
95 assert activities == [unlisted_activity]
96
97 activities =
98 ActivityPub.fetch_activities([], %{:visibility => "private", "actor_id" => user.ap_id})
99
100 assert activities == [private_activity]
101
102 activities =
103 ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id})
104
105 assert activities == [public_activity]
106
107 activities =
108 ActivityPub.fetch_activities([], %{
109 :visibility => ~w[private public],
110 "actor_id" => user.ap_id
111 })
112
113 assert activities == [public_activity, private_activity]
114 end
115 end
116
117 describe "fetching excluded by visibility" do
118 test "it excludes by the appropriate visibility" do
119 user = insert(:user)
120
121 {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
122
123 {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
124
125 {:ok, unlisted_activity} =
126 CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
127
128 {:ok, private_activity} =
129 CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
130
131 activities =
132 ActivityPub.fetch_activities([], %{
133 "exclude_visibilities" => "direct",
134 "actor_id" => user.ap_id
135 })
136
137 assert public_activity in activities
138 assert unlisted_activity in activities
139 assert private_activity in activities
140 refute direct_activity in activities
141
142 activities =
143 ActivityPub.fetch_activities([], %{
144 "exclude_visibilities" => "unlisted",
145 "actor_id" => user.ap_id
146 })
147
148 assert public_activity in activities
149 refute unlisted_activity in activities
150 assert private_activity in activities
151 assert direct_activity in activities
152
153 activities =
154 ActivityPub.fetch_activities([], %{
155 "exclude_visibilities" => "private",
156 "actor_id" => user.ap_id
157 })
158
159 assert public_activity in activities
160 assert unlisted_activity in activities
161 refute private_activity in activities
162 assert direct_activity in activities
163
164 activities =
165 ActivityPub.fetch_activities([], %{
166 "exclude_visibilities" => "public",
167 "actor_id" => user.ap_id
168 })
169
170 refute public_activity in activities
171 assert unlisted_activity in activities
172 assert private_activity in activities
173 assert direct_activity in activities
174 end
175 end
176
177 describe "building a user from his ap id" do
178 test "it returns a user" do
179 user_id = "http://mastodon.example.org/users/admin"
180 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
181 assert user.ap_id == user_id
182 assert user.nickname == "admin@mastodon.example.org"
183 assert user.source_data
184 assert user.ap_enabled
185 assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
186 end
187
188 test "it returns a user that is invisible" do
189 user_id = "http://mastodon.example.org/users/relay"
190 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
191 assert User.invisible?(user)
192 end
193
194 test "it fetches the appropriate tag-restricted posts" do
195 user = insert(:user)
196
197 {:ok, status_one} = CommonAPI.post(user, %{"status" => ". #test"})
198 {:ok, status_two} = CommonAPI.post(user, %{"status" => ". #essais"})
199 {:ok, status_three} = CommonAPI.post(user, %{"status" => ". #test #reject"})
200
201 fetch_one = ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => "test"})
202
203 fetch_two =
204 ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => ["test", "essais"]})
205
206 fetch_three =
207 ActivityPub.fetch_activities([], %{
208 "type" => "Create",
209 "tag" => ["test", "essais"],
210 "tag_reject" => ["reject"]
211 })
212
213 fetch_four =
214 ActivityPub.fetch_activities([], %{
215 "type" => "Create",
216 "tag" => ["test"],
217 "tag_all" => ["test", "reject"]
218 })
219
220 assert fetch_one == [status_one, status_three]
221 assert fetch_two == [status_one, status_two, status_three]
222 assert fetch_three == [status_one, status_two]
223 assert fetch_four == [status_three]
224 end
225 end
226
227 describe "insertion" do
228 test "drops activities beyond a certain limit" do
229 limit = Config.get([:instance, :remote_limit])
230
231 random_text =
232 :crypto.strong_rand_bytes(limit + 1)
233 |> Base.encode64()
234 |> binary_part(0, limit + 1)
235
236 data = %{
237 "ok" => true,
238 "object" => %{
239 "content" => random_text
240 }
241 }
242
243 assert {:error, {:remote_limit_error, _}} = ActivityPub.insert(data)
244 end
245
246 test "doesn't drop activities with content being null" do
247 user = insert(:user)
248
249 data = %{
250 "actor" => user.ap_id,
251 "to" => [],
252 "object" => %{
253 "actor" => user.ap_id,
254 "to" => [],
255 "type" => "Note",
256 "content" => nil
257 }
258 }
259
260 assert {:ok, _} = ActivityPub.insert(data)
261 end
262
263 test "returns the activity if one with the same id is already in" do
264 activity = insert(:note_activity)
265 {:ok, new_activity} = ActivityPub.insert(activity.data)
266
267 assert activity.id == new_activity.id
268 end
269
270 test "inserts a given map into the activity database, giving it an id if it has none." do
271 user = insert(:user)
272
273 data = %{
274 "actor" => user.ap_id,
275 "to" => [],
276 "object" => %{
277 "actor" => user.ap_id,
278 "to" => [],
279 "type" => "Note",
280 "content" => "hey"
281 }
282 }
283
284 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
285 assert activity.data["ok"] == data["ok"]
286 assert is_binary(activity.data["id"])
287
288 given_id = "bla"
289
290 data = %{
291 "id" => given_id,
292 "actor" => user.ap_id,
293 "to" => [],
294 "context" => "blabla",
295 "object" => %{
296 "actor" => user.ap_id,
297 "to" => [],
298 "type" => "Note",
299 "content" => "hey"
300 }
301 }
302
303 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
304 assert activity.data["ok"] == data["ok"]
305 assert activity.data["id"] == given_id
306 assert activity.data["context"] == "blabla"
307 assert activity.data["context_id"]
308 end
309
310 test "adds a context when none is there" do
311 user = insert(:user)
312
313 data = %{
314 "actor" => user.ap_id,
315 "to" => [],
316 "object" => %{
317 "actor" => user.ap_id,
318 "to" => [],
319 "type" => "Note",
320 "content" => "hey"
321 }
322 }
323
324 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
325 object = Pleroma.Object.normalize(activity)
326
327 assert is_binary(activity.data["context"])
328 assert is_binary(object.data["context"])
329 assert activity.data["context_id"]
330 assert object.data["context_id"]
331 end
332
333 test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
334 user = insert(:user)
335
336 data = %{
337 "actor" => user.ap_id,
338 "to" => [],
339 "object" => %{
340 "actor" => user.ap_id,
341 "to" => [],
342 "type" => "Note",
343 "content" => "hey"
344 }
345 }
346
347 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
348 assert object = Object.normalize(activity)
349 assert is_binary(object.data["id"])
350 end
351 end
352
353 describe "listen activities" do
354 test "does not increase user note count" do
355 user = insert(:user)
356
357 {:ok, activity} =
358 ActivityPub.listen(%{
359 to: ["https://www.w3.org/ns/activitystreams#Public"],
360 actor: user,
361 context: "",
362 object: %{
363 "actor" => user.ap_id,
364 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
365 "artist" => "lain",
366 "title" => "lain radio episode 1",
367 "length" => 180_000,
368 "type" => "Audio"
369 }
370 })
371
372 assert activity.actor == user.ap_id
373
374 user = User.get_cached_by_id(user.id)
375 assert user.note_count == 0
376 end
377
378 test "can be fetched into a timeline" do
379 _listen_activity_1 = insert(:listen)
380 _listen_activity_2 = insert(:listen)
381 _listen_activity_3 = insert(:listen)
382
383 timeline = ActivityPub.fetch_activities([], %{"type" => ["Listen"]})
384
385 assert length(timeline) == 3
386 end
387 end
388
389 describe "create activities" do
390 test "it reverts create" do
391 user = insert(:user)
392
393 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
394 assert {:error, :reverted} =
395 ActivityPub.create(%{
396 to: ["user1", "user2"],
397 actor: user,
398 context: "",
399 object: %{
400 "to" => ["user1", "user2"],
401 "type" => "Note",
402 "content" => "testing"
403 }
404 })
405 end
406
407 assert Repo.aggregate(Activity, :count, :id) == 0
408 assert Repo.aggregate(Object, :count, :id) == 0
409 end
410
411 test "removes doubled 'to' recipients" do
412 user = insert(:user)
413
414 {:ok, activity} =
415 ActivityPub.create(%{
416 to: ["user1", "user1", "user2"],
417 actor: user,
418 context: "",
419 object: %{
420 "to" => ["user1", "user1", "user2"],
421 "type" => "Note",
422 "content" => "testing"
423 }
424 })
425
426 assert activity.data["to"] == ["user1", "user2"]
427 assert activity.actor == user.ap_id
428 assert activity.recipients == ["user1", "user2", user.ap_id]
429 end
430
431 test "increases user note count only for public activities" do
432 user = insert(:user)
433
434 {:ok, _} =
435 CommonAPI.post(User.get_cached_by_id(user.id), %{
436 "status" => "1",
437 "visibility" => "public"
438 })
439
440 {:ok, _} =
441 CommonAPI.post(User.get_cached_by_id(user.id), %{
442 "status" => "2",
443 "visibility" => "unlisted"
444 })
445
446 {:ok, _} =
447 CommonAPI.post(User.get_cached_by_id(user.id), %{
448 "status" => "2",
449 "visibility" => "private"
450 })
451
452 {:ok, _} =
453 CommonAPI.post(User.get_cached_by_id(user.id), %{
454 "status" => "3",
455 "visibility" => "direct"
456 })
457
458 user = User.get_cached_by_id(user.id)
459 assert user.note_count == 2
460 end
461
462 test "increases replies count" do
463 user = insert(:user)
464 user2 = insert(:user)
465
466 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
467 ap_id = activity.data["id"]
468 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
469
470 # public
471 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
472 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
473 assert object.data["repliesCount"] == 1
474
475 # unlisted
476 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
477 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
478 assert object.data["repliesCount"] == 2
479
480 # private
481 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
482 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
483 assert object.data["repliesCount"] == 2
484
485 # direct
486 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
487 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
488 assert object.data["repliesCount"] == 2
489 end
490 end
491
492 describe "fetch activities for recipients" do
493 test "retrieve the activities for certain recipients" do
494 {:ok, activity_one} = ActivityBuilder.insert(%{"to" => ["someone"]})
495 {:ok, activity_two} = ActivityBuilder.insert(%{"to" => ["someone_else"]})
496 {:ok, _activity_three} = ActivityBuilder.insert(%{"to" => ["noone"]})
497
498 activities = ActivityPub.fetch_activities(["someone", "someone_else"])
499 assert length(activities) == 2
500 assert activities == [activity_one, activity_two]
501 end
502 end
503
504 describe "fetch activities in context" do
505 test "retrieves activities that have a given context" do
506 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
507 {:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
508 {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
509 {:ok, _activity_four} = ActivityBuilder.insert(%{"type" => "Announce", "context" => "2hu"})
510 activity_five = insert(:note_activity)
511 user = insert(:user)
512
513 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})
514
515 activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})
516 assert activities == [activity_two, activity]
517 end
518 end
519
520 test "doesn't return blocked activities" do
521 activity_one = insert(:note_activity)
522 activity_two = insert(:note_activity)
523 activity_three = insert(:note_activity)
524 user = insert(:user)
525 booster = insert(:user)
526 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
527
528 activities =
529 ActivityPub.fetch_activities([], %{"blocking_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 {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
536
537 activities =
538 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
539
540 assert Enum.member?(activities, activity_two)
541 assert Enum.member?(activities, activity_three)
542 assert Enum.member?(activities, activity_one)
543
544 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})
545 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
546 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
547 activity_three = Activity.get_by_id(activity_three.id)
548
549 activities =
550 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
551
552 assert Enum.member?(activities, activity_two)
553 refute Enum.member?(activities, activity_three)
554 refute Enum.member?(activities, boost_activity)
555 assert Enum.member?(activities, activity_one)
556
557 activities =
558 ActivityPub.fetch_activities([], %{"blocking_user" => nil, "skip_preload" => true})
559
560 assert Enum.member?(activities, activity_two)
561 assert Enum.member?(activities, activity_three)
562 assert Enum.member?(activities, boost_activity)
563 assert Enum.member?(activities, activity_one)
564 end
565
566 test "doesn't return transitive interactions concerning blocked users" do
567 blocker = insert(:user)
568 blockee = insert(:user)
569 friend = insert(:user)
570
571 {:ok, _user_relationship} = User.block(blocker, blockee)
572
573 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
574
575 {:ok, activity_two} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"})
576
577 {:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
578
579 {:ok, activity_four} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"})
580
581 activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
582
583 assert Enum.member?(activities, activity_one)
584 refute Enum.member?(activities, activity_two)
585 refute Enum.member?(activities, activity_three)
586 refute Enum.member?(activities, activity_four)
587 end
588
589 test "doesn't return announce activities concerning blocked users" do
590 blocker = insert(:user)
591 blockee = insert(:user)
592 friend = insert(:user)
593
594 {:ok, _user_relationship} = User.block(blocker, blockee)
595
596 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
597
598 {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
599
600 {:ok, activity_three, _} = CommonAPI.repeat(activity_two.id, friend)
601
602 activities =
603 ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
604 |> Enum.map(fn act -> act.id end)
605
606 assert Enum.member?(activities, activity_one.id)
607 refute Enum.member?(activities, activity_two.id)
608 refute Enum.member?(activities, activity_three.id)
609 end
610
611 test "doesn't return activities from blocked domains" do
612 domain = "dogwhistle.zone"
613 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
614 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
615 activity = insert(:note_activity, %{note: note})
616 user = insert(:user)
617 {:ok, user} = User.block_domain(user, domain)
618
619 activities =
620 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
621
622 refute activity in activities
623
624 followed_user = insert(:user)
625 ActivityPub.follow(user, followed_user)
626 {:ok, repeat_activity, _} = CommonAPI.repeat(activity.id, followed_user)
627
628 activities =
629 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
630
631 refute repeat_activity in activities
632 end
633
634 test "does return activities from followed users on blocked domains" do
635 domain = "meanies.social"
636 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
637 blocker = insert(:user)
638
639 {:ok, blocker} = User.follow(blocker, domain_user)
640 {:ok, blocker} = User.block_domain(blocker, domain)
641
642 assert User.following?(blocker, domain_user)
643 assert User.blocks_domain?(blocker, domain_user)
644 refute User.blocks?(blocker, domain_user)
645
646 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
647 activity = insert(:note_activity, %{note: note})
648
649 activities =
650 ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true})
651
652 assert activity in activities
653
654 # And check that if the guy we DO follow boosts someone else from their domain,
655 # that should be hidden
656 another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"})
657 bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}})
658 bad_activity = insert(:note_activity, %{note: bad_note})
659 {:ok, repeat_activity, _} = CommonAPI.repeat(bad_activity.id, domain_user)
660
661 activities =
662 ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true})
663
664 refute repeat_activity in activities
665 end
666
667 test "doesn't return muted activities" do
668 activity_one = insert(:note_activity)
669 activity_two = insert(:note_activity)
670 activity_three = insert(:note_activity)
671 user = insert(:user)
672 booster = insert(:user)
673
674 activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
675 {:ok, _user_relationships} = User.mute(user, activity_one_actor)
676
677 activities =
678 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
679
680 assert Enum.member?(activities, activity_two)
681 assert Enum.member?(activities, activity_three)
682 refute Enum.member?(activities, activity_one)
683
684 # Calling with 'with_muted' will deliver muted activities, too.
685 activities =
686 ActivityPub.fetch_activities([], %{
687 "muting_user" => user,
688 "with_muted" => true,
689 "skip_preload" => true
690 })
691
692 assert Enum.member?(activities, activity_two)
693 assert Enum.member?(activities, activity_three)
694 assert Enum.member?(activities, activity_one)
695
696 {:ok, _user_mute} = User.unmute(user, activity_one_actor)
697
698 activities =
699 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
700
701 assert Enum.member?(activities, activity_two)
702 assert Enum.member?(activities, activity_three)
703 assert Enum.member?(activities, activity_one)
704
705 activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
706 {:ok, _user_relationships} = User.mute(user, activity_three_actor)
707 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
708 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
709 activity_three = Activity.get_by_id(activity_three.id)
710
711 activities =
712 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
713
714 assert Enum.member?(activities, activity_two)
715 refute Enum.member?(activities, activity_three)
716 refute Enum.member?(activities, boost_activity)
717 assert Enum.member?(activities, activity_one)
718
719 activities = ActivityPub.fetch_activities([], %{"muting_user" => nil, "skip_preload" => true})
720
721 assert Enum.member?(activities, activity_two)
722 assert Enum.member?(activities, activity_three)
723 assert Enum.member?(activities, boost_activity)
724 assert Enum.member?(activities, activity_one)
725 end
726
727 test "doesn't return thread muted activities" do
728 user = insert(:user)
729 _activity_one = insert(:note_activity)
730 note_two = insert(:note, data: %{"context" => "suya.."})
731 activity_two = insert(:note_activity, note: note_two)
732
733 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
734
735 assert [_activity_one] = ActivityPub.fetch_activities([], %{"muting_user" => user})
736 end
737
738 test "returns thread muted activities when with_muted is set" do
739 user = insert(:user)
740 _activity_one = insert(:note_activity)
741 note_two = insert(:note, data: %{"context" => "suya.."})
742 activity_two = insert(:note_activity, note: note_two)
743
744 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
745
746 assert [_activity_two, _activity_one] =
747 ActivityPub.fetch_activities([], %{"muting_user" => user, "with_muted" => true})
748 end
749
750 test "does include announces on request" do
751 activity_three = insert(:note_activity)
752 user = insert(:user)
753 booster = insert(:user)
754
755 {:ok, user} = User.follow(user, booster)
756
757 {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster)
758
759 [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])
760
761 assert announce_activity.id == announce.id
762 end
763
764 test "excludes reblogs on request" do
765 user = insert(:user)
766 {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
767 {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
768
769 [activity] = ActivityPub.fetch_user_activities(user, nil, %{"exclude_reblogs" => "true"})
770
771 assert activity == expected_activity
772 end
773
774 describe "public fetch activities" do
775 test "doesn't retrieve unlisted activities" do
776 user = insert(:user)
777
778 {:ok, _unlisted_activity} =
779 CommonAPI.post(user, %{"status" => "yeah", "visibility" => "unlisted"})
780
781 {:ok, listed_activity} = CommonAPI.post(user, %{"status" => "yeah"})
782
783 [activity] = ActivityPub.fetch_public_activities()
784
785 assert activity == listed_activity
786 end
787
788 test "retrieves public activities" do
789 _activities = ActivityPub.fetch_public_activities()
790
791 %{public: public} = ActivityBuilder.public_and_non_public()
792
793 activities = ActivityPub.fetch_public_activities()
794 assert length(activities) == 1
795 assert Enum.at(activities, 0) == public
796 end
797
798 test "retrieves a maximum of 20 activities" do
799 ActivityBuilder.insert_list(10)
800 expected_activities = ActivityBuilder.insert_list(20)
801
802 activities = ActivityPub.fetch_public_activities()
803
804 assert collect_ids(activities) == collect_ids(expected_activities)
805 assert length(activities) == 20
806 end
807
808 test "retrieves ids starting from a since_id" do
809 activities = ActivityBuilder.insert_list(30)
810 expected_activities = ActivityBuilder.insert_list(10)
811 since_id = List.last(activities).id
812
813 activities = ActivityPub.fetch_public_activities(%{"since_id" => since_id})
814
815 assert collect_ids(activities) == collect_ids(expected_activities)
816 assert length(activities) == 10
817 end
818
819 test "retrieves ids up to max_id" do
820 ActivityBuilder.insert_list(10)
821 expected_activities = ActivityBuilder.insert_list(20)
822
823 %{id: max_id} =
824 10
825 |> ActivityBuilder.insert_list()
826 |> List.first()
827
828 activities = ActivityPub.fetch_public_activities(%{"max_id" => max_id})
829
830 assert length(activities) == 20
831 assert collect_ids(activities) == collect_ids(expected_activities)
832 end
833
834 test "paginates via offset/limit" do
835 _first_part_activities = ActivityBuilder.insert_list(10)
836 second_part_activities = ActivityBuilder.insert_list(10)
837
838 later_activities = ActivityBuilder.insert_list(10)
839
840 activities =
841 ActivityPub.fetch_public_activities(%{"page" => "2", "page_size" => "20"}, :offset)
842
843 assert length(activities) == 20
844
845 assert collect_ids(activities) ==
846 collect_ids(second_part_activities) ++ collect_ids(later_activities)
847 end
848
849 test "doesn't return reblogs for users for whom reblogs have been muted" do
850 activity = insert(:note_activity)
851 user = insert(:user)
852 booster = insert(:user)
853 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
854
855 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
856
857 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
858
859 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
860 end
861
862 test "returns reblogs for users for whom reblogs have not been muted" do
863 activity = insert(:note_activity)
864 user = insert(:user)
865 booster = insert(:user)
866 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
867 {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
868
869 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
870
871 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
872
873 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
874 end
875 end
876
877 describe "react to an object" do
878 test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
879 Config.put([:instance, :federating], true)
880 user = insert(:user)
881 reactor = insert(:user)
882 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
883 assert object = Object.normalize(activity)
884
885 {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
886
887 assert called(Federator.publish(reaction_activity))
888 end
889
890 test "adds an emoji reaction activity to the db" do
891 user = insert(:user)
892 reactor = insert(:user)
893 third_user = insert(:user)
894 fourth_user = insert(:user)
895 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
896 assert object = Object.normalize(activity)
897
898 {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
899
900 assert reaction_activity
901
902 assert reaction_activity.data["actor"] == reactor.ap_id
903 assert reaction_activity.data["type"] == "EmojiReact"
904 assert reaction_activity.data["content"] == "🔥"
905 assert reaction_activity.data["object"] == object.data["id"]
906 assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]]
907 assert reaction_activity.data["context"] == object.data["context"]
908 assert object.data["reaction_count"] == 1
909 assert object.data["reactions"] == [["🔥", [reactor.ap_id]]]
910
911 {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(third_user, object, "☕")
912
913 assert object.data["reaction_count"] == 2
914 assert object.data["reactions"] == [["🔥", [reactor.ap_id]], ["☕", [third_user.ap_id]]]
915
916 {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(fourth_user, object, "🔥")
917
918 assert object.data["reaction_count"] == 3
919
920 assert object.data["reactions"] == [
921 ["🔥", [fourth_user.ap_id, reactor.ap_id]],
922 ["☕", [third_user.ap_id]]
923 ]
924 end
925
926 test "reverts emoji reaction on error" do
927 [user, reactor] = insert_list(2, :user)
928
929 {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"})
930 object = Object.normalize(activity)
931
932 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
933 assert {:error, :reverted} = ActivityPub.react_with_emoji(reactor, object, "😀")
934 end
935
936 object = Object.get_by_ap_id(object.data["id"])
937 refute object.data["reaction_count"]
938 refute object.data["reactions"]
939 end
940 end
941
942 describe "unreacting to an object" do
943 test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
944 Config.put([:instance, :federating], true)
945 user = insert(:user)
946 reactor = insert(:user)
947 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
948 assert object = Object.normalize(activity)
949
950 {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
951
952 assert called(Federator.publish(reaction_activity))
953
954 {:ok, unreaction_activity, _object} =
955 ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
956
957 assert called(Federator.publish(unreaction_activity))
958 end
959
960 test "adds an undo activity to the db" do
961 user = insert(:user)
962 reactor = insert(:user)
963 {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
964 assert object = Object.normalize(activity)
965
966 {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
967
968 {:ok, unreaction_activity, _object} =
969 ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
970
971 assert unreaction_activity.actor == reactor.ap_id
972 assert unreaction_activity.data["object"] == reaction_activity.data["id"]
973
974 object = Object.get_by_ap_id(object.data["id"])
975 assert object.data["reaction_count"] == 0
976 assert object.data["reactions"] == []
977 end
978
979 test "reverts emoji unreact on error" do
980 [user, reactor] = insert_list(2, :user)
981 {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"})
982 object = Object.normalize(activity)
983
984 {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "😀")
985
986 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
987 assert {:error, :reverted} =
988 ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
989 end
990
991 object = Object.get_by_ap_id(object.data["id"])
992
993 assert object.data["reaction_count"] == 1
994 assert object.data["reactions"] == [["😀", [reactor.ap_id]]]
995 end
996 end
997
998 describe "like an object" do
999 test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
1000 Config.put([:instance, :federating], true)
1001 note_activity = insert(:note_activity)
1002 assert object_activity = Object.normalize(note_activity)
1003
1004 user = insert(:user)
1005
1006 {:ok, like_activity, _object} = ActivityPub.like(user, object_activity)
1007 assert called(Federator.publish(like_activity))
1008 end
1009
1010 test "returns exist activity if object already liked" do
1011 note_activity = insert(:note_activity)
1012 assert object_activity = Object.normalize(note_activity)
1013
1014 user = insert(:user)
1015
1016 {:ok, like_activity, _object} = ActivityPub.like(user, object_activity)
1017
1018 {:ok, like_activity_exist, _object} = ActivityPub.like(user, object_activity)
1019 assert like_activity == like_activity_exist
1020 end
1021
1022 test "reverts like activity on error" do
1023 note_activity = insert(:note_activity)
1024 object = Object.normalize(note_activity)
1025 user = insert(:user)
1026
1027 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1028 assert {:error, :reverted} = ActivityPub.like(user, object)
1029 end
1030
1031 assert Repo.aggregate(Activity, :count, :id) == 1
1032 assert Repo.get(Object, object.id) == object
1033 end
1034
1035 test "adds a like activity to the db" do
1036 note_activity = insert(:note_activity)
1037 assert object = Object.normalize(note_activity)
1038
1039 user = insert(:user)
1040 user_two = insert(:user)
1041
1042 {:ok, like_activity, object} = ActivityPub.like(user, object)
1043
1044 assert like_activity.data["actor"] == user.ap_id
1045 assert like_activity.data["type"] == "Like"
1046 assert like_activity.data["object"] == object.data["id"]
1047 assert like_activity.data["to"] == [User.ap_followers(user), note_activity.data["actor"]]
1048 assert like_activity.data["context"] == object.data["context"]
1049 assert object.data["like_count"] == 1
1050 assert object.data["likes"] == [user.ap_id]
1051
1052 # Just return the original activity if the user already liked it.
1053 {:ok, same_like_activity, object} = ActivityPub.like(user, object)
1054
1055 assert like_activity == same_like_activity
1056 assert object.data["likes"] == [user.ap_id]
1057 assert object.data["like_count"] == 1
1058
1059 {:ok, _like_activity, object} = ActivityPub.like(user_two, object)
1060 assert object.data["like_count"] == 2
1061 end
1062 end
1063
1064 describe "unliking" do
1065 test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
1066 Config.put([:instance, :federating], true)
1067
1068 note_activity = insert(:note_activity)
1069 object = Object.normalize(note_activity)
1070 user = insert(:user)
1071
1072 {:ok, object} = ActivityPub.unlike(user, object)
1073 refute called(Federator.publish())
1074
1075 {:ok, _like_activity, object} = ActivityPub.like(user, object)
1076 assert object.data["like_count"] == 1
1077
1078 {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
1079 assert object.data["like_count"] == 0
1080
1081 assert called(Federator.publish(unlike_activity))
1082 end
1083
1084 test "reverts unliking on error" do
1085 note_activity = insert(:note_activity)
1086 object = Object.normalize(note_activity)
1087 user = insert(:user)
1088
1089 {:ok, like_activity, object} = ActivityPub.like(user, object)
1090 assert object.data["like_count"] == 1
1091
1092 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1093 assert {:error, :reverted} = ActivityPub.unlike(user, object)
1094 end
1095
1096 assert Object.get_by_ap_id(object.data["id"]) == object
1097 assert object.data["like_count"] == 1
1098 assert Activity.get_by_id(like_activity.id)
1099 end
1100
1101 test "unliking a previously liked object" do
1102 note_activity = insert(:note_activity)
1103 object = Object.normalize(note_activity)
1104 user = insert(:user)
1105
1106 # Unliking something that hasn't been liked does nothing
1107 {:ok, object} = ActivityPub.unlike(user, object)
1108 assert object.data["like_count"] == 0
1109
1110 {:ok, like_activity, object} = ActivityPub.like(user, object)
1111 assert object.data["like_count"] == 1
1112
1113 {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
1114 assert object.data["like_count"] == 0
1115
1116 assert Activity.get_by_id(like_activity.id) == nil
1117 assert note_activity.actor in unlike_activity.recipients
1118 end
1119 end
1120
1121 describe "announcing an object" do
1122 test "adds an announce activity to the db" do
1123 note_activity = insert(:note_activity)
1124 object = Object.normalize(note_activity)
1125 user = insert(:user)
1126
1127 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
1128 assert object.data["announcement_count"] == 1
1129 assert object.data["announcements"] == [user.ap_id]
1130
1131 assert announce_activity.data["to"] == [
1132 User.ap_followers(user),
1133 note_activity.data["actor"]
1134 ]
1135
1136 assert announce_activity.data["object"] == object.data["id"]
1137 assert announce_activity.data["actor"] == user.ap_id
1138 assert announce_activity.data["context"] == object.data["context"]
1139 end
1140
1141 test "reverts annouce from object on error" do
1142 note_activity = insert(:note_activity)
1143 object = Object.normalize(note_activity)
1144 user = insert(:user)
1145
1146 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1147 assert {:error, :reverted} = ActivityPub.announce(user, object)
1148 end
1149
1150 reloaded_object = Object.get_by_ap_id(object.data["id"])
1151 assert reloaded_object == object
1152 refute reloaded_object.data["announcement_count"]
1153 refute reloaded_object.data["announcements"]
1154 end
1155 end
1156
1157 describe "announcing a private object" do
1158 test "adds an announce activity to the db if the audience is not widened" do
1159 user = insert(:user)
1160 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1161 object = Object.normalize(note_activity)
1162
1163 {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false)
1164
1165 assert announce_activity.data["to"] == [User.ap_followers(user)]
1166
1167 assert announce_activity.data["object"] == object.data["id"]
1168 assert announce_activity.data["actor"] == user.ap_id
1169 assert announce_activity.data["context"] == object.data["context"]
1170 end
1171
1172 test "does not add an announce activity to the db if the audience is widened" do
1173 user = insert(:user)
1174 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1175 object = Object.normalize(note_activity)
1176
1177 assert {:error, _} = ActivityPub.announce(user, object, nil, true, true)
1178 end
1179
1180 test "does not add an announce activity to the db if the announcer is not the author" do
1181 user = insert(:user)
1182 announcer = insert(:user)
1183 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1184 object = Object.normalize(note_activity)
1185
1186 assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false)
1187 end
1188 end
1189
1190 describe "unannouncing an object" do
1191 test "unannouncing a previously announced object" do
1192 note_activity = insert(:note_activity)
1193 object = Object.normalize(note_activity)
1194 user = insert(:user)
1195
1196 # Unannouncing an object that is not announced does nothing
1197 {:ok, object} = ActivityPub.unannounce(user, object)
1198 refute object.data["announcement_count"]
1199
1200 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
1201 assert object.data["announcement_count"] == 1
1202
1203 {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
1204 assert object.data["announcement_count"] == 0
1205
1206 assert unannounce_activity.data["to"] == [
1207 User.ap_followers(user),
1208 object.data["actor"]
1209 ]
1210
1211 assert unannounce_activity.data["type"] == "Undo"
1212 assert unannounce_activity.data["object"] == announce_activity.data
1213 assert unannounce_activity.data["actor"] == user.ap_id
1214 assert unannounce_activity.data["context"] == announce_activity.data["context"]
1215
1216 assert Activity.get_by_id(announce_activity.id) == nil
1217 end
1218
1219 test "reverts unannouncing on error" do
1220 note_activity = insert(:note_activity)
1221 object = Object.normalize(note_activity)
1222 user = insert(:user)
1223
1224 {:ok, _announce_activity, object} = ActivityPub.announce(user, object)
1225 assert object.data["announcement_count"] == 1
1226
1227 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1228 assert {:error, :reverted} = ActivityPub.unannounce(user, object)
1229 end
1230
1231 object = Object.get_by_ap_id(object.data["id"])
1232 assert object.data["announcement_count"] == 1
1233 end
1234 end
1235
1236 describe "uploading files" do
1237 test "copies the file to the configured folder" do
1238 file = %Plug.Upload{
1239 content_type: "image/jpg",
1240 path: Path.absname("test/fixtures/image.jpg"),
1241 filename: "an_image.jpg"
1242 }
1243
1244 {:ok, %Object{} = object} = ActivityPub.upload(file)
1245 assert object.data["name"] == "an_image.jpg"
1246 end
1247
1248 test "works with base64 encoded images" do
1249 file = %{
1250 "img" => data_uri()
1251 }
1252
1253 {:ok, %Object{}} = ActivityPub.upload(file)
1254 end
1255 end
1256
1257 describe "fetch the latest Follow" do
1258 test "fetches the latest Follow activity" do
1259 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1260 follower = Repo.get_by(User, ap_id: activity.data["actor"])
1261 followed = Repo.get_by(User, ap_id: activity.data["object"])
1262
1263 assert activity == Utils.fetch_latest_follow(follower, followed)
1264 end
1265 end
1266
1267 describe "following / unfollowing" do
1268 test "it reverts follow activity" do
1269 follower = insert(:user)
1270 followed = insert(:user)
1271
1272 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1273 assert {:error, :reverted} = ActivityPub.follow(follower, followed)
1274 end
1275
1276 assert Repo.aggregate(Activity, :count, :id) == 0
1277 assert Repo.aggregate(Object, :count, :id) == 0
1278 end
1279
1280 test "it reverts unfollow activity" do
1281 follower = insert(:user)
1282 followed = insert(:user)
1283
1284 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1285
1286 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1287 assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1288 end
1289
1290 activity = Activity.get_by_id(follow_activity.id)
1291 assert activity.data["type"] == "Follow"
1292 assert activity.data["actor"] == follower.ap_id
1293
1294 assert activity.data["object"] == followed.ap_id
1295 end
1296
1297 test "creates a follow activity" do
1298 follower = insert(:user)
1299 followed = insert(:user)
1300
1301 {:ok, activity} = ActivityPub.follow(follower, followed)
1302 assert activity.data["type"] == "Follow"
1303 assert activity.data["actor"] == follower.ap_id
1304 assert activity.data["object"] == followed.ap_id
1305 end
1306
1307 test "creates an undo activity for the last follow" do
1308 follower = insert(:user)
1309 followed = insert(:user)
1310
1311 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1312 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1313
1314 assert activity.data["type"] == "Undo"
1315 assert activity.data["actor"] == follower.ap_id
1316
1317 embedded_object = activity.data["object"]
1318 assert is_map(embedded_object)
1319 assert embedded_object["type"] == "Follow"
1320 assert embedded_object["object"] == followed.ap_id
1321 assert embedded_object["id"] == follow_activity.data["id"]
1322 end
1323
1324 test "creates an undo activity for a pending follow request" do
1325 follower = insert(:user)
1326 followed = insert(:user, %{locked: true})
1327
1328 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1329 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1330
1331 assert activity.data["type"] == "Undo"
1332 assert activity.data["actor"] == follower.ap_id
1333
1334 embedded_object = activity.data["object"]
1335 assert is_map(embedded_object)
1336 assert embedded_object["type"] == "Follow"
1337 assert embedded_object["object"] == followed.ap_id
1338 assert embedded_object["id"] == follow_activity.data["id"]
1339 end
1340 end
1341
1342 describe "blocking / unblocking" do
1343 test "reverts block activity on error" do
1344 [blocker, blocked] = insert_list(2, :user)
1345
1346 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1347 assert {:error, :reverted} = ActivityPub.block(blocker, blocked)
1348 end
1349
1350 assert Repo.aggregate(Activity, :count, :id) == 0
1351 assert Repo.aggregate(Object, :count, :id) == 0
1352 end
1353
1354 clear_config([:instance, :federating])
1355
1356 test "creates a block activity" do
1357 Config.put([:instance, :federating], true)
1358 blocker = insert(:user)
1359 blocked = insert(:user)
1360
1361 with_mock Pleroma.Web.Federator,
1362 publish: fn _ -> nil end do
1363 {:ok, activity} = ActivityPub.block(blocker, blocked)
1364
1365 assert activity.data["type"] == "Block"
1366 assert activity.data["actor"] == blocker.ap_id
1367 assert activity.data["object"] == blocked.ap_id
1368
1369 assert called(Pleroma.Web.Federator.publish(activity))
1370 end
1371 end
1372
1373 clear_config([:instance, :federating])
1374 clear_config([:activitypub, :outgoing_blocks])
1375
1376 test "works with outgoing blocks disabled, but doesn't federate" do
1377 Config.put([:instance, :federating], true)
1378 Config.put([:activitypub, :outgoing_blocks], false)
1379 blocker = insert(:user)
1380 blocked = insert(:user)
1381
1382 with_mock Pleroma.Web.Federator,
1383 publish: fn _ -> nil end do
1384 {:ok, activity} = ActivityPub.block(blocker, blocked)
1385
1386 assert activity.data["type"] == "Block"
1387 assert activity.data["actor"] == blocker.ap_id
1388 assert activity.data["object"] == blocked.ap_id
1389
1390 refute called(Pleroma.Web.Federator.publish(:_))
1391 end
1392 end
1393
1394 test "reverts unblock activity on error" do
1395 [blocker, blocked] = insert_list(2, :user)
1396 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1397
1398 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1399 assert {:error, :reverted} = ActivityPub.unblock(blocker, blocked)
1400 end
1401
1402 assert block_activity.data["type"] == "Block"
1403 assert block_activity.data["actor"] == blocker.ap_id
1404
1405 assert Repo.aggregate(Activity, :count, :id) == 1
1406 assert Repo.aggregate(Object, :count, :id) == 1
1407 end
1408
1409 test "creates an undo activity for the last block" do
1410 blocker = insert(:user)
1411 blocked = insert(:user)
1412
1413 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1414 {:ok, activity} = ActivityPub.unblock(blocker, blocked)
1415
1416 assert activity.data["type"] == "Undo"
1417 assert activity.data["actor"] == blocker.ap_id
1418
1419 embedded_object = activity.data["object"]
1420 assert is_map(embedded_object)
1421 assert embedded_object["type"] == "Block"
1422 assert embedded_object["object"] == blocked.ap_id
1423 assert embedded_object["id"] == block_activity.data["id"]
1424 end
1425 end
1426
1427 describe "deletion" do
1428 clear_config([:instance, :rewrite_policy])
1429
1430 test "it reverts deletion on error" do
1431 note = insert(:note_activity)
1432 object = Object.normalize(note)
1433
1434 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1435 assert {:error, :reverted} = ActivityPub.delete(object)
1436 end
1437
1438 assert Repo.aggregate(Activity, :count, :id) == 1
1439 assert Repo.get(Object, object.id) == object
1440 assert Activity.get_by_id(note.id) == note
1441 end
1442
1443 test "it creates a delete activity and deletes the original object" do
1444 note = insert(:note_activity)
1445 object = Object.normalize(note)
1446 {:ok, delete} = ActivityPub.delete(object)
1447
1448 assert delete.data["type"] == "Delete"
1449 assert delete.data["actor"] == note.data["actor"]
1450 assert delete.data["object"] == object.data["id"]
1451
1452 assert Activity.get_by_id(delete.id) != nil
1453
1454 assert Repo.get(Object, object.id).data["type"] == "Tombstone"
1455 end
1456
1457 test "it doesn't fail when an activity was already deleted" do
1458 {:ok, delete} = insert(:note_activity) |> Object.normalize() |> ActivityPub.delete()
1459
1460 assert {:ok, ^delete} = delete |> Object.normalize() |> ActivityPub.delete()
1461 end
1462
1463 test "decrements user note count only for public activities" do
1464 user = insert(:user, note_count: 10)
1465
1466 {:ok, a1} =
1467 CommonAPI.post(User.get_cached_by_id(user.id), %{
1468 "status" => "yeah",
1469 "visibility" => "public"
1470 })
1471
1472 {:ok, a2} =
1473 CommonAPI.post(User.get_cached_by_id(user.id), %{
1474 "status" => "yeah",
1475 "visibility" => "unlisted"
1476 })
1477
1478 {:ok, a3} =
1479 CommonAPI.post(User.get_cached_by_id(user.id), %{
1480 "status" => "yeah",
1481 "visibility" => "private"
1482 })
1483
1484 {:ok, a4} =
1485 CommonAPI.post(User.get_cached_by_id(user.id), %{
1486 "status" => "yeah",
1487 "visibility" => "direct"
1488 })
1489
1490 {:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
1491 {:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
1492 {:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
1493 {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
1494
1495 user = User.get_cached_by_id(user.id)
1496 assert user.note_count == 10
1497 end
1498
1499 test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
1500 user = insert(:user)
1501 note = insert(:note_activity)
1502 object = Object.normalize(note)
1503
1504 {:ok, object} =
1505 object
1506 |> Object.change(%{
1507 data: %{
1508 "actor" => object.data["actor"],
1509 "id" => object.data["id"],
1510 "to" => [user.ap_id],
1511 "type" => "Note"
1512 }
1513 })
1514 |> Object.update_and_set_cache()
1515
1516 {:ok, delete} = ActivityPub.delete(object)
1517
1518 assert user.ap_id in delete.data["to"]
1519 end
1520
1521 test "decreases reply count" do
1522 user = insert(:user)
1523 user2 = insert(:user)
1524
1525 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
1526 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
1527 ap_id = activity.data["id"]
1528
1529 {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
1530 {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
1531 {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
1532 {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
1533
1534 _ = CommonAPI.delete(direct_reply.id, user2)
1535 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1536 assert object.data["repliesCount"] == 2
1537
1538 _ = CommonAPI.delete(private_reply.id, user2)
1539 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1540 assert object.data["repliesCount"] == 2
1541
1542 _ = CommonAPI.delete(public_reply.id, user2)
1543 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1544 assert object.data["repliesCount"] == 1
1545
1546 _ = CommonAPI.delete(unlisted_reply.id, user2)
1547 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1548 assert object.data["repliesCount"] == 0
1549 end
1550
1551 test "it passes delete activity through MRF before deleting the object" do
1552 Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy)
1553
1554 note = insert(:note_activity)
1555 object = Object.normalize(note)
1556
1557 {:error, {:reject, _}} = ActivityPub.delete(object)
1558
1559 assert Activity.get_by_id(note.id)
1560 assert Repo.get(Object, object.id).data["type"] == object.data["type"]
1561 end
1562 end
1563
1564 describe "timeline post-processing" do
1565 test "it filters broken threads" do
1566 user1 = insert(:user)
1567 user2 = insert(:user)
1568 user3 = insert(:user)
1569
1570 {:ok, user1} = User.follow(user1, user3)
1571 assert User.following?(user1, user3)
1572
1573 {:ok, user2} = User.follow(user2, user3)
1574 assert User.following?(user2, user3)
1575
1576 {:ok, user3} = User.follow(user3, user2)
1577 assert User.following?(user3, user2)
1578
1579 {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"})
1580
1581 {:ok, private_activity_1} =
1582 CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
1583
1584 {:ok, private_activity_2} =
1585 CommonAPI.post(user2, %{
1586 "status" => "hi 3",
1587 "visibility" => "private",
1588 "in_reply_to_status_id" => private_activity_1.id
1589 })
1590
1591 {:ok, private_activity_3} =
1592 CommonAPI.post(user3, %{
1593 "status" => "hi 4",
1594 "visibility" => "private",
1595 "in_reply_to_status_id" => private_activity_2.id
1596 })
1597
1598 activities =
1599 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1600 |> Enum.map(fn a -> a.id end)
1601
1602 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1603
1604 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1605
1606 assert length(activities) == 3
1607
1608 activities =
1609 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{"user" => user1})
1610 |> Enum.map(fn a -> a.id end)
1611
1612 assert [public_activity.id, private_activity_1.id] == activities
1613 assert length(activities) == 2
1614 end
1615 end
1616
1617 describe "update" do
1618 clear_config([:instance, :max_pinned_statuses])
1619
1620 test "it creates an update activity with the new user data" do
1621 user = insert(:user)
1622 {:ok, user} = User.ensure_keys_present(user)
1623 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
1624
1625 {:ok, update} =
1626 ActivityPub.update(%{
1627 actor: user_data["id"],
1628 to: [user.follower_address],
1629 cc: [],
1630 object: user_data
1631 })
1632
1633 assert update.data["actor"] == user.ap_id
1634 assert update.data["to"] == [user.follower_address]
1635 assert embedded_object = update.data["object"]
1636 assert embedded_object["id"] == user_data["id"]
1637 assert embedded_object["type"] == user_data["type"]
1638 end
1639 end
1640
1641 test "returned pinned statuses" do
1642 Config.put([:instance, :max_pinned_statuses], 3)
1643 user = insert(:user)
1644
1645 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
1646 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
1647 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
1648
1649 CommonAPI.pin(activity_one.id, user)
1650 user = refresh_record(user)
1651
1652 CommonAPI.pin(activity_two.id, user)
1653 user = refresh_record(user)
1654
1655 CommonAPI.pin(activity_three.id, user)
1656 user = refresh_record(user)
1657
1658 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
1659
1660 assert 3 = length(activities)
1661 end
1662
1663 describe "flag/1" do
1664 setup do
1665 reporter = insert(:user)
1666 target_account = insert(:user)
1667 content = "foobar"
1668 {:ok, activity} = CommonAPI.post(target_account, %{"status" => content})
1669 context = Utils.generate_context_id()
1670
1671 reporter_ap_id = reporter.ap_id
1672 target_ap_id = target_account.ap_id
1673 activity_ap_id = activity.data["id"]
1674
1675 activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1676
1677 {:ok,
1678 %{
1679 reporter: reporter,
1680 context: context,
1681 target_account: target_account,
1682 reported_activity: activity,
1683 content: content,
1684 activity_ap_id: activity_ap_id,
1685 activity_with_object: activity_with_object,
1686 reporter_ap_id: reporter_ap_id,
1687 target_ap_id: target_ap_id
1688 }}
1689 end
1690
1691 test "it can create a Flag activity",
1692 %{
1693 reporter: reporter,
1694 context: context,
1695 target_account: target_account,
1696 reported_activity: reported_activity,
1697 content: content,
1698 activity_ap_id: activity_ap_id,
1699 activity_with_object: activity_with_object,
1700 reporter_ap_id: reporter_ap_id,
1701 target_ap_id: target_ap_id
1702 } do
1703 assert {:ok, activity} =
1704 ActivityPub.flag(%{
1705 actor: reporter,
1706 context: context,
1707 account: target_account,
1708 statuses: [reported_activity],
1709 content: content
1710 })
1711
1712 note_obj = %{
1713 "type" => "Note",
1714 "id" => activity_ap_id,
1715 "content" => content,
1716 "published" => activity_with_object.object.data["published"],
1717 "actor" => AccountView.render("show.json", %{user: target_account})
1718 }
1719
1720 assert %Activity{
1721 actor: ^reporter_ap_id,
1722 data: %{
1723 "type" => "Flag",
1724 "content" => ^content,
1725 "context" => ^context,
1726 "object" => [^target_ap_id, ^note_obj]
1727 }
1728 } = activity
1729 end
1730
1731 test_with_mock "strips status data from Flag, before federating it",
1732 %{
1733 reporter: reporter,
1734 context: context,
1735 target_account: target_account,
1736 reported_activity: reported_activity,
1737 content: content
1738 },
1739 Utils,
1740 [:passthrough],
1741 [] do
1742 {:ok, activity} =
1743 ActivityPub.flag(%{
1744 actor: reporter,
1745 context: context,
1746 account: target_account,
1747 statuses: [reported_activity],
1748 content: content
1749 })
1750
1751 new_data =
1752 put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]])
1753
1754 assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1755 end
1756 end
1757
1758 test "fetch_activities/2 returns activities addressed to a list " do
1759 user = insert(:user)
1760 member = insert(:user)
1761 {:ok, list} = Pleroma.List.create("foo", user)
1762 {:ok, list} = Pleroma.List.follow(list, member)
1763
1764 {:ok, activity} =
1765 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1766
1767 activity = Repo.preload(activity, :bookmark)
1768 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1769
1770 assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
1771 end
1772
1773 def data_uri do
1774 File.read!("test/fixtures/avatar_data_uri")
1775 end
1776
1777 describe "fetch_activities_bounded" do
1778 test "fetches private posts for followed users" do
1779 user = insert(:user)
1780
1781 {:ok, activity} =
1782 CommonAPI.post(user, %{
1783 "status" => "thought I looked cute might delete later :3",
1784 "visibility" => "private"
1785 })
1786
1787 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1788 assert result.id == activity.id
1789 end
1790
1791 test "fetches only public posts for other users" do
1792 user = insert(:user)
1793 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
1794
1795 {:ok, _private_activity} =
1796 CommonAPI.post(user, %{
1797 "status" => "why is tenshi eating a corndog so cute?",
1798 "visibility" => "private"
1799 })
1800
1801 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1802 assert result.id == activity.id
1803 end
1804 end
1805
1806 describe "fetch_follow_information_for_user" do
1807 test "syncronizes following/followers counters" do
1808 user =
1809 insert(:user,
1810 local: false,
1811 follower_address: "http://localhost:4001/users/fuser2/followers",
1812 following_address: "http://localhost:4001/users/fuser2/following"
1813 )
1814
1815 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1816 assert info.follower_count == 527
1817 assert info.following_count == 267
1818 end
1819
1820 test "detects hidden followers" do
1821 mock(fn env ->
1822 case env.url do
1823 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1824 %Tesla.Env{status: 403, body: ""}
1825
1826 _ ->
1827 apply(HttpRequestMock, :request, [env])
1828 end
1829 end)
1830
1831 user =
1832 insert(:user,
1833 local: false,
1834 follower_address: "http://localhost:4001/users/masto_closed/followers",
1835 following_address: "http://localhost:4001/users/masto_closed/following"
1836 )
1837
1838 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1839 assert follow_info.hide_followers == true
1840 assert follow_info.hide_follows == false
1841 end
1842
1843 test "detects hidden follows" do
1844 mock(fn env ->
1845 case env.url do
1846 "http://localhost:4001/users/masto_closed/following?page=1" ->
1847 %Tesla.Env{status: 403, body: ""}
1848
1849 _ ->
1850 apply(HttpRequestMock, :request, [env])
1851 end
1852 end)
1853
1854 user =
1855 insert(:user,
1856 local: false,
1857 follower_address: "http://localhost:4001/users/masto_closed/followers",
1858 following_address: "http://localhost:4001/users/masto_closed/following"
1859 )
1860
1861 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1862 assert follow_info.hide_followers == false
1863 assert follow_info.hide_follows == true
1864 end
1865
1866 test "detects hidden follows/followers for friendica" do
1867 user =
1868 insert(:user,
1869 local: false,
1870 follower_address: "http://localhost:8080/followers/fuser3",
1871 following_address: "http://localhost:8080/following/fuser3"
1872 )
1873
1874 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1875 assert follow_info.hide_followers == true
1876 assert follow_info.follower_count == 296
1877 assert follow_info.following_count == 32
1878 assert follow_info.hide_follows == true
1879 end
1880
1881 test "doesn't crash when follower and following counters are hidden" do
1882 mock(fn env ->
1883 case env.url do
1884 "http://localhost:4001/users/masto_hidden_counters/following" ->
1885 json(%{
1886 "@context" => "https://www.w3.org/ns/activitystreams",
1887 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1888 })
1889
1890 "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1891 %Tesla.Env{status: 403, body: ""}
1892
1893 "http://localhost:4001/users/masto_hidden_counters/followers" ->
1894 json(%{
1895 "@context" => "https://www.w3.org/ns/activitystreams",
1896 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1897 })
1898
1899 "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1900 %Tesla.Env{status: 403, body: ""}
1901 end
1902 end)
1903
1904 user =
1905 insert(:user,
1906 local: false,
1907 follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1908 following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1909 )
1910
1911 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1912
1913 assert follow_info.hide_followers == true
1914 assert follow_info.follower_count == 0
1915 assert follow_info.hide_follows == true
1916 assert follow_info.following_count == 0
1917 end
1918 end
1919
1920 describe "fetch_favourites/3" do
1921 test "returns a favourite activities sorted by adds to favorite" do
1922 user = insert(:user)
1923 other_user = insert(:user)
1924 user1 = insert(:user)
1925 user2 = insert(:user)
1926 {:ok, a1} = CommonAPI.post(user1, %{"status" => "bla"})
1927 {:ok, _a2} = CommonAPI.post(user2, %{"status" => "traps are happy"})
1928 {:ok, a3} = CommonAPI.post(user2, %{"status" => "Trees Are "})
1929 {:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "})
1930 {:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "})
1931
1932 {:ok, _, _} = CommonAPI.favorite(a4.id, user)
1933 {:ok, _, _} = CommonAPI.favorite(a3.id, other_user)
1934 {:ok, _, _} = CommonAPI.favorite(a3.id, user)
1935 {:ok, _, _} = CommonAPI.favorite(a5.id, other_user)
1936 {:ok, _, _} = CommonAPI.favorite(a5.id, user)
1937 {:ok, _, _} = CommonAPI.favorite(a4.id, other_user)
1938 {:ok, _, _} = CommonAPI.favorite(a1.id, user)
1939 {:ok, _, _} = CommonAPI.favorite(a1.id, other_user)
1940 result = ActivityPub.fetch_favourites(user)
1941
1942 assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1943
1944 result = ActivityPub.fetch_favourites(user, %{"limit" => 2})
1945 assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1946 end
1947 end
1948
1949 describe "Move activity" do
1950 test "create" do
1951 %{ap_id: old_ap_id} = old_user = insert(:user)
1952 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1953 follower = insert(:user)
1954 follower_move_opted_out = insert(:user, allow_following_move: false)
1955
1956 User.follow(follower, old_user)
1957 User.follow(follower_move_opted_out, old_user)
1958
1959 assert User.following?(follower, old_user)
1960 assert User.following?(follower_move_opted_out, old_user)
1961
1962 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1963
1964 assert %Activity{
1965 actor: ^old_ap_id,
1966 data: %{
1967 "actor" => ^old_ap_id,
1968 "object" => ^old_ap_id,
1969 "target" => ^new_ap_id,
1970 "type" => "Move"
1971 },
1972 local: true
1973 } = activity
1974
1975 params = %{
1976 "op" => "move_following",
1977 "origin_id" => old_user.id,
1978 "target_id" => new_user.id
1979 }
1980
1981 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1982
1983 Pleroma.Workers.BackgroundWorker.perform(params, nil)
1984
1985 refute User.following?(follower, old_user)
1986 assert User.following?(follower, new_user)
1987
1988 assert User.following?(follower_move_opted_out, old_user)
1989 refute User.following?(follower_move_opted_out, new_user)
1990
1991 activity = %Activity{activity | object: nil}
1992
1993 assert [%Notification{activity: ^activity}] =
1994 Notification.for_user(follower, %{with_move: true})
1995
1996 assert [%Notification{activity: ^activity}] =
1997 Notification.for_user(follower_move_opted_out, %{with_move: true})
1998 end
1999
2000 test "old user must be in the new user's `also_known_as` list" do
2001 old_user = insert(:user)
2002 new_user = insert(:user)
2003
2004 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
2005 ActivityPub.move(old_user, new_user)
2006 end
2007 end
2008 end