Merge branch 'develop' into global-status-expiration
[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 "unliking" do
998 test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
999 Config.put([:instance, :federating], true)
1000
1001 note_activity = insert(:note_activity)
1002 object = Object.normalize(note_activity)
1003 user = insert(:user)
1004
1005 {:ok, object} = ActivityPub.unlike(user, object)
1006 refute called(Federator.publish())
1007
1008 {:ok, _like_activity} = CommonAPI.favorite(user, note_activity.id)
1009 object = Object.get_by_id(object.id)
1010 assert object.data["like_count"] == 1
1011
1012 {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
1013 assert object.data["like_count"] == 0
1014
1015 assert called(Federator.publish(unlike_activity))
1016 end
1017
1018 test "reverts unliking on error" do
1019 note_activity = insert(:note_activity)
1020 user = insert(:user)
1021
1022 {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
1023 object = Object.normalize(note_activity)
1024 assert object.data["like_count"] == 1
1025
1026 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1027 assert {:error, :reverted} = ActivityPub.unlike(user, object)
1028 end
1029
1030 assert Object.get_by_ap_id(object.data["id"]) == object
1031 assert object.data["like_count"] == 1
1032 assert Activity.get_by_id(like_activity.id)
1033 end
1034
1035 test "unliking a previously liked object" do
1036 note_activity = insert(:note_activity)
1037 object = Object.normalize(note_activity)
1038 user = insert(:user)
1039
1040 # Unliking something that hasn't been liked does nothing
1041 {:ok, object} = ActivityPub.unlike(user, object)
1042 assert object.data["like_count"] == 0
1043
1044 {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
1045
1046 object = Object.get_by_id(object.id)
1047 assert object.data["like_count"] == 1
1048
1049 {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
1050 assert object.data["like_count"] == 0
1051
1052 assert Activity.get_by_id(like_activity.id) == nil
1053 assert note_activity.actor in unlike_activity.recipients
1054 end
1055 end
1056
1057 describe "announcing an object" do
1058 test "adds an announce activity to the db" do
1059 note_activity = insert(:note_activity)
1060 object = Object.normalize(note_activity)
1061 user = insert(:user)
1062
1063 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
1064 assert object.data["announcement_count"] == 1
1065 assert object.data["announcements"] == [user.ap_id]
1066
1067 assert announce_activity.data["to"] == [
1068 User.ap_followers(user),
1069 note_activity.data["actor"]
1070 ]
1071
1072 assert announce_activity.data["object"] == object.data["id"]
1073 assert announce_activity.data["actor"] == user.ap_id
1074 assert announce_activity.data["context"] == object.data["context"]
1075 end
1076
1077 test "reverts annouce from object on error" do
1078 note_activity = insert(:note_activity)
1079 object = Object.normalize(note_activity)
1080 user = insert(:user)
1081
1082 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1083 assert {:error, :reverted} = ActivityPub.announce(user, object)
1084 end
1085
1086 reloaded_object = Object.get_by_ap_id(object.data["id"])
1087 assert reloaded_object == object
1088 refute reloaded_object.data["announcement_count"]
1089 refute reloaded_object.data["announcements"]
1090 end
1091 end
1092
1093 describe "announcing a private object" do
1094 test "adds an announce activity to the db if the audience is not widened" do
1095 user = insert(:user)
1096 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1097 object = Object.normalize(note_activity)
1098
1099 {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false)
1100
1101 assert announce_activity.data["to"] == [User.ap_followers(user)]
1102
1103 assert announce_activity.data["object"] == object.data["id"]
1104 assert announce_activity.data["actor"] == user.ap_id
1105 assert announce_activity.data["context"] == object.data["context"]
1106 end
1107
1108 test "does not add an announce activity to the db if the audience is widened" do
1109 user = insert(:user)
1110 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1111 object = Object.normalize(note_activity)
1112
1113 assert {:error, _} = ActivityPub.announce(user, object, nil, true, true)
1114 end
1115
1116 test "does not add an announce activity to the db if the announcer is not the author" do
1117 user = insert(:user)
1118 announcer = insert(:user)
1119 {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
1120 object = Object.normalize(note_activity)
1121
1122 assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false)
1123 end
1124 end
1125
1126 describe "unannouncing an object" do
1127 test "unannouncing a previously announced object" do
1128 note_activity = insert(:note_activity)
1129 object = Object.normalize(note_activity)
1130 user = insert(:user)
1131
1132 # Unannouncing an object that is not announced does nothing
1133 {:ok, object} = ActivityPub.unannounce(user, object)
1134 refute object.data["announcement_count"]
1135
1136 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
1137 assert object.data["announcement_count"] == 1
1138
1139 {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
1140 assert object.data["announcement_count"] == 0
1141
1142 assert unannounce_activity.data["to"] == [
1143 User.ap_followers(user),
1144 object.data["actor"]
1145 ]
1146
1147 assert unannounce_activity.data["type"] == "Undo"
1148 assert unannounce_activity.data["object"] == announce_activity.data
1149 assert unannounce_activity.data["actor"] == user.ap_id
1150 assert unannounce_activity.data["context"] == announce_activity.data["context"]
1151
1152 assert Activity.get_by_id(announce_activity.id) == nil
1153 end
1154
1155 test "reverts unannouncing on error" do
1156 note_activity = insert(:note_activity)
1157 object = Object.normalize(note_activity)
1158 user = insert(:user)
1159
1160 {:ok, _announce_activity, object} = ActivityPub.announce(user, object)
1161 assert object.data["announcement_count"] == 1
1162
1163 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1164 assert {:error, :reverted} = ActivityPub.unannounce(user, object)
1165 end
1166
1167 object = Object.get_by_ap_id(object.data["id"])
1168 assert object.data["announcement_count"] == 1
1169 end
1170 end
1171
1172 describe "uploading files" do
1173 test "copies the file to the configured folder" do
1174 file = %Plug.Upload{
1175 content_type: "image/jpg",
1176 path: Path.absname("test/fixtures/image.jpg"),
1177 filename: "an_image.jpg"
1178 }
1179
1180 {:ok, %Object{} = object} = ActivityPub.upload(file)
1181 assert object.data["name"] == "an_image.jpg"
1182 end
1183
1184 test "works with base64 encoded images" do
1185 file = %{
1186 "img" => data_uri()
1187 }
1188
1189 {:ok, %Object{}} = ActivityPub.upload(file)
1190 end
1191 end
1192
1193 describe "fetch the latest Follow" do
1194 test "fetches the latest Follow activity" do
1195 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1196 follower = Repo.get_by(User, ap_id: activity.data["actor"])
1197 followed = Repo.get_by(User, ap_id: activity.data["object"])
1198
1199 assert activity == Utils.fetch_latest_follow(follower, followed)
1200 end
1201 end
1202
1203 describe "following / unfollowing" do
1204 test "it reverts follow activity" do
1205 follower = insert(:user)
1206 followed = insert(:user)
1207
1208 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1209 assert {:error, :reverted} = ActivityPub.follow(follower, followed)
1210 end
1211
1212 assert Repo.aggregate(Activity, :count, :id) == 0
1213 assert Repo.aggregate(Object, :count, :id) == 0
1214 end
1215
1216 test "it reverts unfollow activity" do
1217 follower = insert(:user)
1218 followed = insert(:user)
1219
1220 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1221
1222 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1223 assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1224 end
1225
1226 activity = Activity.get_by_id(follow_activity.id)
1227 assert activity.data["type"] == "Follow"
1228 assert activity.data["actor"] == follower.ap_id
1229
1230 assert activity.data["object"] == followed.ap_id
1231 end
1232
1233 test "creates a follow activity" do
1234 follower = insert(:user)
1235 followed = insert(:user)
1236
1237 {:ok, activity} = ActivityPub.follow(follower, followed)
1238 assert activity.data["type"] == "Follow"
1239 assert activity.data["actor"] == follower.ap_id
1240 assert activity.data["object"] == followed.ap_id
1241 end
1242
1243 test "creates an undo activity for the last follow" do
1244 follower = insert(:user)
1245 followed = insert(:user)
1246
1247 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1248 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1249
1250 assert activity.data["type"] == "Undo"
1251 assert activity.data["actor"] == follower.ap_id
1252
1253 embedded_object = activity.data["object"]
1254 assert is_map(embedded_object)
1255 assert embedded_object["type"] == "Follow"
1256 assert embedded_object["object"] == followed.ap_id
1257 assert embedded_object["id"] == follow_activity.data["id"]
1258 end
1259
1260 test "creates an undo activity for a pending follow request" do
1261 follower = insert(:user)
1262 followed = insert(:user, %{locked: true})
1263
1264 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1265 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1266
1267 assert activity.data["type"] == "Undo"
1268 assert activity.data["actor"] == follower.ap_id
1269
1270 embedded_object = activity.data["object"]
1271 assert is_map(embedded_object)
1272 assert embedded_object["type"] == "Follow"
1273 assert embedded_object["object"] == followed.ap_id
1274 assert embedded_object["id"] == follow_activity.data["id"]
1275 end
1276 end
1277
1278 describe "blocking / unblocking" do
1279 test "reverts block activity on error" do
1280 [blocker, blocked] = insert_list(2, :user)
1281
1282 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1283 assert {:error, :reverted} = ActivityPub.block(blocker, blocked)
1284 end
1285
1286 assert Repo.aggregate(Activity, :count, :id) == 0
1287 assert Repo.aggregate(Object, :count, :id) == 0
1288 end
1289
1290 test "creates a block activity" do
1291 blocker = insert(:user)
1292 blocked = insert(:user)
1293
1294 {:ok, activity} = ActivityPub.block(blocker, blocked)
1295
1296 assert activity.data["type"] == "Block"
1297 assert activity.data["actor"] == blocker.ap_id
1298 assert activity.data["object"] == blocked.ap_id
1299 end
1300
1301 test "reverts unblock activity on error" do
1302 [blocker, blocked] = insert_list(2, :user)
1303 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1304
1305 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1306 assert {:error, :reverted} = ActivityPub.unblock(blocker, blocked)
1307 end
1308
1309 assert block_activity.data["type"] == "Block"
1310 assert block_activity.data["actor"] == blocker.ap_id
1311
1312 assert Repo.aggregate(Activity, :count, :id) == 1
1313 assert Repo.aggregate(Object, :count, :id) == 1
1314 end
1315
1316 test "creates an undo activity for the last block" do
1317 blocker = insert(:user)
1318 blocked = insert(:user)
1319
1320 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
1321 {:ok, activity} = ActivityPub.unblock(blocker, blocked)
1322
1323 assert activity.data["type"] == "Undo"
1324 assert activity.data["actor"] == blocker.ap_id
1325
1326 embedded_object = activity.data["object"]
1327 assert is_map(embedded_object)
1328 assert embedded_object["type"] == "Block"
1329 assert embedded_object["object"] == blocked.ap_id
1330 assert embedded_object["id"] == block_activity.data["id"]
1331 end
1332 end
1333
1334 describe "deletion" do
1335 setup do: clear_config([:instance, :rewrite_policy])
1336
1337 test "it reverts deletion on error" do
1338 note = insert(:note_activity)
1339 object = Object.normalize(note)
1340
1341 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1342 assert {:error, :reverted} = ActivityPub.delete(object)
1343 end
1344
1345 assert Repo.aggregate(Activity, :count, :id) == 1
1346 assert Repo.get(Object, object.id) == object
1347 assert Activity.get_by_id(note.id) == note
1348 end
1349
1350 test "it creates a delete activity and deletes the original object" do
1351 note = insert(:note_activity)
1352 object = Object.normalize(note)
1353 {:ok, delete} = ActivityPub.delete(object)
1354
1355 assert delete.data["type"] == "Delete"
1356 assert delete.data["actor"] == note.data["actor"]
1357 assert delete.data["object"] == object.data["id"]
1358
1359 assert Activity.get_by_id(delete.id) != nil
1360
1361 assert Repo.get(Object, object.id).data["type"] == "Tombstone"
1362 end
1363
1364 test "it doesn't fail when an activity was already deleted" do
1365 {:ok, delete} = insert(:note_activity) |> Object.normalize() |> ActivityPub.delete()
1366
1367 assert {:ok, ^delete} = delete |> Object.normalize() |> ActivityPub.delete()
1368 end
1369
1370 test "decrements user note count only for public activities" do
1371 user = insert(:user, note_count: 10)
1372
1373 {:ok, a1} =
1374 CommonAPI.post(User.get_cached_by_id(user.id), %{
1375 "status" => "yeah",
1376 "visibility" => "public"
1377 })
1378
1379 {:ok, a2} =
1380 CommonAPI.post(User.get_cached_by_id(user.id), %{
1381 "status" => "yeah",
1382 "visibility" => "unlisted"
1383 })
1384
1385 {:ok, a3} =
1386 CommonAPI.post(User.get_cached_by_id(user.id), %{
1387 "status" => "yeah",
1388 "visibility" => "private"
1389 })
1390
1391 {:ok, a4} =
1392 CommonAPI.post(User.get_cached_by_id(user.id), %{
1393 "status" => "yeah",
1394 "visibility" => "direct"
1395 })
1396
1397 {:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
1398 {:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
1399 {:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
1400 {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
1401
1402 user = User.get_cached_by_id(user.id)
1403 assert user.note_count == 10
1404 end
1405
1406 test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
1407 user = insert(:user)
1408 note = insert(:note_activity)
1409 object = Object.normalize(note)
1410
1411 {:ok, object} =
1412 object
1413 |> Object.change(%{
1414 data: %{
1415 "actor" => object.data["actor"],
1416 "id" => object.data["id"],
1417 "to" => [user.ap_id],
1418 "type" => "Note"
1419 }
1420 })
1421 |> Object.update_and_set_cache()
1422
1423 {:ok, delete} = ActivityPub.delete(object)
1424
1425 assert user.ap_id in delete.data["to"]
1426 end
1427
1428 test "decreases reply count" do
1429 user = insert(:user)
1430 user2 = insert(:user)
1431
1432 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
1433 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
1434 ap_id = activity.data["id"]
1435
1436 {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
1437 {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
1438 {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
1439 {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
1440
1441 _ = CommonAPI.delete(direct_reply.id, user2)
1442 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1443 assert object.data["repliesCount"] == 2
1444
1445 _ = CommonAPI.delete(private_reply.id, user2)
1446 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1447 assert object.data["repliesCount"] == 2
1448
1449 _ = CommonAPI.delete(public_reply.id, user2)
1450 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1451 assert object.data["repliesCount"] == 1
1452
1453 _ = CommonAPI.delete(unlisted_reply.id, user2)
1454 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1455 assert object.data["repliesCount"] == 0
1456 end
1457
1458 test "it passes delete activity through MRF before deleting the object" do
1459 Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy)
1460
1461 note = insert(:note_activity)
1462 object = Object.normalize(note)
1463
1464 {:error, {:reject, _}} = ActivityPub.delete(object)
1465
1466 assert Activity.get_by_id(note.id)
1467 assert Repo.get(Object, object.id).data["type"] == object.data["type"]
1468 end
1469 end
1470
1471 describe "timeline post-processing" do
1472 test "it filters broken threads" do
1473 user1 = insert(:user)
1474 user2 = insert(:user)
1475 user3 = insert(:user)
1476
1477 {:ok, user1} = User.follow(user1, user3)
1478 assert User.following?(user1, user3)
1479
1480 {:ok, user2} = User.follow(user2, user3)
1481 assert User.following?(user2, user3)
1482
1483 {:ok, user3} = User.follow(user3, user2)
1484 assert User.following?(user3, user2)
1485
1486 {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"})
1487
1488 {:ok, private_activity_1} =
1489 CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
1490
1491 {:ok, private_activity_2} =
1492 CommonAPI.post(user2, %{
1493 "status" => "hi 3",
1494 "visibility" => "private",
1495 "in_reply_to_status_id" => private_activity_1.id
1496 })
1497
1498 {:ok, private_activity_3} =
1499 CommonAPI.post(user3, %{
1500 "status" => "hi 4",
1501 "visibility" => "private",
1502 "in_reply_to_status_id" => private_activity_2.id
1503 })
1504
1505 activities =
1506 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1507 |> Enum.map(fn a -> a.id end)
1508
1509 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1510
1511 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1512
1513 assert length(activities) == 3
1514
1515 activities =
1516 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{"user" => user1})
1517 |> Enum.map(fn a -> a.id end)
1518
1519 assert [public_activity.id, private_activity_1.id] == activities
1520 assert length(activities) == 2
1521 end
1522 end
1523
1524 describe "update" do
1525 setup do: clear_config([:instance, :max_pinned_statuses])
1526
1527 test "it creates an update activity with the new user data" do
1528 user = insert(:user)
1529 {:ok, user} = User.ensure_keys_present(user)
1530 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
1531
1532 {:ok, update} =
1533 ActivityPub.update(%{
1534 actor: user_data["id"],
1535 to: [user.follower_address],
1536 cc: [],
1537 object: user_data
1538 })
1539
1540 assert update.data["actor"] == user.ap_id
1541 assert update.data["to"] == [user.follower_address]
1542 assert embedded_object = update.data["object"]
1543 assert embedded_object["id"] == user_data["id"]
1544 assert embedded_object["type"] == user_data["type"]
1545 end
1546 end
1547
1548 test "returned pinned statuses" do
1549 Config.put([:instance, :max_pinned_statuses], 3)
1550 user = insert(:user)
1551
1552 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
1553 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
1554 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
1555
1556 CommonAPI.pin(activity_one.id, user)
1557 user = refresh_record(user)
1558
1559 CommonAPI.pin(activity_two.id, user)
1560 user = refresh_record(user)
1561
1562 CommonAPI.pin(activity_three.id, user)
1563 user = refresh_record(user)
1564
1565 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
1566
1567 assert 3 = length(activities)
1568 end
1569
1570 describe "flag/1" do
1571 setup do
1572 reporter = insert(:user)
1573 target_account = insert(:user)
1574 content = "foobar"
1575 {:ok, activity} = CommonAPI.post(target_account, %{"status" => content})
1576 context = Utils.generate_context_id()
1577
1578 reporter_ap_id = reporter.ap_id
1579 target_ap_id = target_account.ap_id
1580 activity_ap_id = activity.data["id"]
1581
1582 activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1583
1584 {:ok,
1585 %{
1586 reporter: reporter,
1587 context: context,
1588 target_account: target_account,
1589 reported_activity: activity,
1590 content: content,
1591 activity_ap_id: activity_ap_id,
1592 activity_with_object: activity_with_object,
1593 reporter_ap_id: reporter_ap_id,
1594 target_ap_id: target_ap_id
1595 }}
1596 end
1597
1598 test "it can create a Flag activity",
1599 %{
1600 reporter: reporter,
1601 context: context,
1602 target_account: target_account,
1603 reported_activity: reported_activity,
1604 content: content,
1605 activity_ap_id: activity_ap_id,
1606 activity_with_object: activity_with_object,
1607 reporter_ap_id: reporter_ap_id,
1608 target_ap_id: target_ap_id
1609 } do
1610 assert {:ok, activity} =
1611 ActivityPub.flag(%{
1612 actor: reporter,
1613 context: context,
1614 account: target_account,
1615 statuses: [reported_activity],
1616 content: content
1617 })
1618
1619 note_obj = %{
1620 "type" => "Note",
1621 "id" => activity_ap_id,
1622 "content" => content,
1623 "published" => activity_with_object.object.data["published"],
1624 "actor" => AccountView.render("show.json", %{user: target_account})
1625 }
1626
1627 assert %Activity{
1628 actor: ^reporter_ap_id,
1629 data: %{
1630 "type" => "Flag",
1631 "content" => ^content,
1632 "context" => ^context,
1633 "object" => [^target_ap_id, ^note_obj]
1634 }
1635 } = activity
1636 end
1637
1638 test_with_mock "strips status data from Flag, before federating it",
1639 %{
1640 reporter: reporter,
1641 context: context,
1642 target_account: target_account,
1643 reported_activity: reported_activity,
1644 content: content
1645 },
1646 Utils,
1647 [:passthrough],
1648 [] do
1649 {:ok, activity} =
1650 ActivityPub.flag(%{
1651 actor: reporter,
1652 context: context,
1653 account: target_account,
1654 statuses: [reported_activity],
1655 content: content
1656 })
1657
1658 new_data =
1659 put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]])
1660
1661 assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1662 end
1663 end
1664
1665 test "fetch_activities/2 returns activities addressed to a list " do
1666 user = insert(:user)
1667 member = insert(:user)
1668 {:ok, list} = Pleroma.List.create("foo", user)
1669 {:ok, list} = Pleroma.List.follow(list, member)
1670
1671 {:ok, activity} =
1672 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1673
1674 activity = Repo.preload(activity, :bookmark)
1675 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1676
1677 assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
1678 end
1679
1680 def data_uri do
1681 File.read!("test/fixtures/avatar_data_uri")
1682 end
1683
1684 describe "fetch_activities_bounded" do
1685 test "fetches private posts for followed users" do
1686 user = insert(:user)
1687
1688 {:ok, activity} =
1689 CommonAPI.post(user, %{
1690 "status" => "thought I looked cute might delete later :3",
1691 "visibility" => "private"
1692 })
1693
1694 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1695 assert result.id == activity.id
1696 end
1697
1698 test "fetches only public posts for other users" do
1699 user = insert(:user)
1700 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
1701
1702 {:ok, _private_activity} =
1703 CommonAPI.post(user, %{
1704 "status" => "why is tenshi eating a corndog so cute?",
1705 "visibility" => "private"
1706 })
1707
1708 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1709 assert result.id == activity.id
1710 end
1711 end
1712
1713 describe "fetch_follow_information_for_user" do
1714 test "syncronizes following/followers counters" do
1715 user =
1716 insert(:user,
1717 local: false,
1718 follower_address: "http://localhost:4001/users/fuser2/followers",
1719 following_address: "http://localhost:4001/users/fuser2/following"
1720 )
1721
1722 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1723 assert info.follower_count == 527
1724 assert info.following_count == 267
1725 end
1726
1727 test "detects hidden followers" do
1728 mock(fn env ->
1729 case env.url do
1730 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1731 %Tesla.Env{status: 403, body: ""}
1732
1733 _ ->
1734 apply(HttpRequestMock, :request, [env])
1735 end
1736 end)
1737
1738 user =
1739 insert(:user,
1740 local: false,
1741 follower_address: "http://localhost:4001/users/masto_closed/followers",
1742 following_address: "http://localhost:4001/users/masto_closed/following"
1743 )
1744
1745 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1746 assert follow_info.hide_followers == true
1747 assert follow_info.hide_follows == false
1748 end
1749
1750 test "detects hidden follows" do
1751 mock(fn env ->
1752 case env.url do
1753 "http://localhost:4001/users/masto_closed/following?page=1" ->
1754 %Tesla.Env{status: 403, body: ""}
1755
1756 _ ->
1757 apply(HttpRequestMock, :request, [env])
1758 end
1759 end)
1760
1761 user =
1762 insert(:user,
1763 local: false,
1764 follower_address: "http://localhost:4001/users/masto_closed/followers",
1765 following_address: "http://localhost:4001/users/masto_closed/following"
1766 )
1767
1768 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1769 assert follow_info.hide_followers == false
1770 assert follow_info.hide_follows == true
1771 end
1772
1773 test "detects hidden follows/followers for friendica" do
1774 user =
1775 insert(:user,
1776 local: false,
1777 follower_address: "http://localhost:8080/followers/fuser3",
1778 following_address: "http://localhost:8080/following/fuser3"
1779 )
1780
1781 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1782 assert follow_info.hide_followers == true
1783 assert follow_info.follower_count == 296
1784 assert follow_info.following_count == 32
1785 assert follow_info.hide_follows == true
1786 end
1787
1788 test "doesn't crash when follower and following counters are hidden" do
1789 mock(fn env ->
1790 case env.url do
1791 "http://localhost:4001/users/masto_hidden_counters/following" ->
1792 json(%{
1793 "@context" => "https://www.w3.org/ns/activitystreams",
1794 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1795 })
1796
1797 "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1798 %Tesla.Env{status: 403, body: ""}
1799
1800 "http://localhost:4001/users/masto_hidden_counters/followers" ->
1801 json(%{
1802 "@context" => "https://www.w3.org/ns/activitystreams",
1803 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1804 })
1805
1806 "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1807 %Tesla.Env{status: 403, body: ""}
1808 end
1809 end)
1810
1811 user =
1812 insert(:user,
1813 local: false,
1814 follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1815 following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1816 )
1817
1818 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1819
1820 assert follow_info.hide_followers == true
1821 assert follow_info.follower_count == 0
1822 assert follow_info.hide_follows == true
1823 assert follow_info.following_count == 0
1824 end
1825 end
1826
1827 describe "fetch_favourites/3" do
1828 test "returns a favourite activities sorted by adds to favorite" do
1829 user = insert(:user)
1830 other_user = insert(:user)
1831 user1 = insert(:user)
1832 user2 = insert(:user)
1833 {:ok, a1} = CommonAPI.post(user1, %{"status" => "bla"})
1834 {:ok, _a2} = CommonAPI.post(user2, %{"status" => "traps are happy"})
1835 {:ok, a3} = CommonAPI.post(user2, %{"status" => "Trees Are "})
1836 {:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "})
1837 {:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "})
1838
1839 {:ok, _} = CommonAPI.favorite(user, a4.id)
1840 {:ok, _} = CommonAPI.favorite(other_user, a3.id)
1841 {:ok, _} = CommonAPI.favorite(user, a3.id)
1842 {:ok, _} = CommonAPI.favorite(other_user, a5.id)
1843 {:ok, _} = CommonAPI.favorite(user, a5.id)
1844 {:ok, _} = CommonAPI.favorite(other_user, a4.id)
1845 {:ok, _} = CommonAPI.favorite(user, a1.id)
1846 {:ok, _} = CommonAPI.favorite(other_user, a1.id)
1847 result = ActivityPub.fetch_favourites(user)
1848
1849 assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1850
1851 result = ActivityPub.fetch_favourites(user, %{"limit" => 2})
1852 assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1853 end
1854 end
1855
1856 describe "Move activity" do
1857 test "create" do
1858 %{ap_id: old_ap_id} = old_user = insert(:user)
1859 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1860 follower = insert(:user)
1861 follower_move_opted_out = insert(:user, allow_following_move: false)
1862
1863 User.follow(follower, old_user)
1864 User.follow(follower_move_opted_out, old_user)
1865
1866 assert User.following?(follower, old_user)
1867 assert User.following?(follower_move_opted_out, old_user)
1868
1869 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1870
1871 assert %Activity{
1872 actor: ^old_ap_id,
1873 data: %{
1874 "actor" => ^old_ap_id,
1875 "object" => ^old_ap_id,
1876 "target" => ^new_ap_id,
1877 "type" => "Move"
1878 },
1879 local: true
1880 } = activity
1881
1882 params = %{
1883 "op" => "move_following",
1884 "origin_id" => old_user.id,
1885 "target_id" => new_user.id
1886 }
1887
1888 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1889
1890 Pleroma.Workers.BackgroundWorker.perform(params, nil)
1891
1892 refute User.following?(follower, old_user)
1893 assert User.following?(follower, new_user)
1894
1895 assert User.following?(follower_move_opted_out, old_user)
1896 refute User.following?(follower_move_opted_out, new_user)
1897
1898 activity = %Activity{activity | object: nil}
1899
1900 assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1901
1902 assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1903 end
1904
1905 test "old user must be in the new user's `also_known_as` list" do
1906 old_user = insert(:user)
1907 new_user = insert(:user)
1908
1909 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1910 ActivityPub.move(old_user, new_user)
1911 end
1912 end
1913
1914 describe "global activity expiration" do
1915 setup do: clear_config([:instance, :rewrite_policy])
1916
1917 test "creates an activity expiration for local Create activities" do
1918 Pleroma.Config.put(
1919 [:instance, :rewrite_policy],
1920 Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy
1921 )
1922
1923 {:ok, %{id: id_create}} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
1924 {:ok, _follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
1925
1926 assert [%{activity_id: ^id_create}] = Pleroma.ActivityExpiration |> Repo.all()
1927 end
1928 end
1929 end