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