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