Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into chat-federation...
[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
20 import ExUnit.CaptureLog
21 import Mock
22 import Pleroma.Factory
23 import Tesla.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} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
82
83 {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
84
85 activities = ActivityPub.fetch_activities([], %{visibility: "direct", actor_id: user.ap_id})
86
87 assert activities == [direct_activity]
88
89 activities =
90 ActivityPub.fetch_activities([], %{visibility: "unlisted", actor_id: user.ap_id})
91
92 assert activities == [unlisted_activity]
93
94 activities =
95 ActivityPub.fetch_activities([], %{visibility: "private", actor_id: user.ap_id})
96
97 assert activities == [private_activity]
98
99 activities = ActivityPub.fetch_activities([], %{visibility: "public", actor_id: user.ap_id})
100
101 assert activities == [public_activity]
102
103 activities =
104 ActivityPub.fetch_activities([], %{
105 visibility: ~w[private public],
106 actor_id: user.ap_id
107 })
108
109 assert activities == [public_activity, private_activity]
110 end
111 end
112
113 describe "fetching excluded by visibility" do
114 test "it excludes by the appropriate visibility" do
115 user = insert(:user)
116
117 {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
118
119 {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
120
121 {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
122
123 {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
124
125 activities =
126 ActivityPub.fetch_activities([], %{
127 exclude_visibilities: "direct",
128 actor_id: user.ap_id
129 })
130
131 assert public_activity in activities
132 assert unlisted_activity in activities
133 assert private_activity in activities
134 refute direct_activity in activities
135
136 activities =
137 ActivityPub.fetch_activities([], %{
138 exclude_visibilities: "unlisted",
139 actor_id: user.ap_id
140 })
141
142 assert public_activity in activities
143 refute unlisted_activity in activities
144 assert private_activity in activities
145 assert direct_activity in activities
146
147 activities =
148 ActivityPub.fetch_activities([], %{
149 exclude_visibilities: "private",
150 actor_id: user.ap_id
151 })
152
153 assert public_activity in activities
154 assert unlisted_activity in activities
155 refute private_activity in activities
156 assert direct_activity in activities
157
158 activities =
159 ActivityPub.fetch_activities([], %{
160 exclude_visibilities: "public",
161 actor_id: user.ap_id
162 })
163
164 refute public_activity in activities
165 assert unlisted_activity in activities
166 assert private_activity in activities
167 assert direct_activity in activities
168 end
169 end
170
171 describe "building a user from his ap id" do
172 test "it returns a user" do
173 user_id = "http://mastodon.example.org/users/admin"
174 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
175 assert user.ap_id == user_id
176 assert user.nickname == "admin@mastodon.example.org"
177 assert user.ap_enabled
178 assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
179 end
180
181 test "it returns a user that is invisible" do
182 user_id = "http://mastodon.example.org/users/relay"
183 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
184 assert User.invisible?(user)
185 end
186
187 test "it returns a user that accepts chat messages" do
188 user_id = "http://mastodon.example.org/users/admin"
189 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
190
191 assert user.accepts_chat_messages
192 end
193 end
194
195 test "it fetches the appropriate tag-restricted posts" do
196 user = insert(:user)
197
198 {:ok, status_one} = CommonAPI.post(user, %{status: ". #test"})
199 {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
200 {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
201
202 fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
203
204 fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]})
205
206 fetch_three =
207 ActivityPub.fetch_activities([], %{
208 type: "Create",
209 tag: ["test", "essais"],
210 tag_reject: ["reject"]
211 })
212
213 fetch_four =
214 ActivityPub.fetch_activities([], %{
215 type: "Create",
216 tag: ["test"],
217 tag_all: ["test", "reject"]
218 })
219
220 assert fetch_one == [status_one, status_three]
221 assert fetch_two == [status_one, status_two, status_three]
222 assert fetch_three == [status_one, status_two]
223 assert fetch_four == [status_three]
224 end
225
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
518 test "doesn't return activities with filtered words" do
519 user = insert(:user)
520 user_two = insert(:user)
521 insert(:filter, user: user, phrase: "test", hide: true)
522
523 {:ok, %{id: id1, data: %{"context" => context}}} = CommonAPI.post(user, %{status: "1"})
524
525 {:ok, %{id: id2}} = CommonAPI.post(user_two, %{status: "2", in_reply_to_status_id: id1})
526
527 {:ok, %{id: id3} = user_activity} =
528 CommonAPI.post(user, %{status: "3 test?", in_reply_to_status_id: id2})
529
530 {:ok, %{id: id4} = filtered_activity} =
531 CommonAPI.post(user_two, %{status: "4 test!", in_reply_to_status_id: id3})
532
533 {:ok, _} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
534
535 activities =
536 context
537 |> ActivityPub.fetch_activities_for_context(%{user: user})
538 |> Enum.map(& &1.id)
539
540 assert length(activities) == 4
541 assert user_activity.id in activities
542 refute filtered_activity.id in activities
543 end
544 end
545
546 test "doesn't return blocked activities" do
547 activity_one = insert(:note_activity)
548 activity_two = insert(:note_activity)
549 activity_three = insert(:note_activity)
550 user = insert(:user)
551 booster = insert(:user)
552 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
553
554 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
555
556 assert Enum.member?(activities, activity_two)
557 assert Enum.member?(activities, activity_three)
558 refute Enum.member?(activities, activity_one)
559
560 {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
561
562 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
563
564 assert Enum.member?(activities, activity_two)
565 assert Enum.member?(activities, activity_three)
566 assert Enum.member?(activities, activity_one)
567
568 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})
569 {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
570 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
571 activity_three = Activity.get_by_id(activity_three.id)
572
573 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
574
575 assert Enum.member?(activities, activity_two)
576 refute Enum.member?(activities, activity_three)
577 refute Enum.member?(activities, boost_activity)
578 assert Enum.member?(activities, activity_one)
579
580 activities = ActivityPub.fetch_activities([], %{blocking_user: nil, skip_preload: true})
581
582 assert Enum.member?(activities, activity_two)
583 assert Enum.member?(activities, activity_three)
584 assert Enum.member?(activities, boost_activity)
585 assert Enum.member?(activities, activity_one)
586 end
587
588 test "doesn't return transitive interactions 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(friend, %{status: "hey! @#{blockee.nickname}"})
598
599 {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
600
601 {:ok, activity_four} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
602
603 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker})
604
605 assert Enum.member?(activities, activity_one)
606 refute Enum.member?(activities, activity_two)
607 refute Enum.member?(activities, activity_three)
608 refute Enum.member?(activities, activity_four)
609 end
610
611 test "doesn't return announce activities with blocked users in 'to'" do
612 blocker = insert(:user)
613 blockee = insert(:user)
614 friend = insert(:user)
615
616 {:ok, _user_relationship} = User.block(blocker, blockee)
617
618 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
619
620 {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
621
622 {:ok, activity_three} = CommonAPI.repeat(activity_two.id, friend)
623
624 activities =
625 ActivityPub.fetch_activities([], %{blocking_user: blocker})
626 |> Enum.map(fn act -> act.id end)
627
628 assert Enum.member?(activities, activity_one.id)
629 refute Enum.member?(activities, activity_two.id)
630 refute Enum.member?(activities, activity_three.id)
631 end
632
633 test "doesn't return announce activities with blocked users in 'cc'" do
634 blocker = insert(:user)
635 blockee = insert(:user)
636 friend = insert(:user)
637
638 {:ok, _user_relationship} = User.block(blocker, blockee)
639
640 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
641
642 {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
643
644 assert object = Pleroma.Object.normalize(activity_two)
645
646 data = %{
647 "actor" => friend.ap_id,
648 "object" => object.data["id"],
649 "context" => object.data["context"],
650 "type" => "Announce",
651 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
652 "cc" => [blockee.ap_id]
653 }
654
655 assert {:ok, activity_three} = ActivityPub.insert(data)
656
657 activities =
658 ActivityPub.fetch_activities([], %{blocking_user: blocker})
659 |> Enum.map(fn act -> act.id end)
660
661 assert Enum.member?(activities, activity_one.id)
662 refute Enum.member?(activities, activity_two.id)
663 refute Enum.member?(activities, activity_three.id)
664 end
665
666 test "doesn't return activities from blocked domains" do
667 domain = "dogwhistle.zone"
668 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
669 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
670 activity = insert(:note_activity, %{note: note})
671 user = insert(:user)
672 {:ok, user} = User.block_domain(user, domain)
673
674 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
675
676 refute activity in activities
677
678 followed_user = insert(:user)
679 CommonAPI.follow(user, followed_user)
680 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user)
681
682 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
683
684 refute repeat_activity in activities
685 end
686
687 test "does return activities from followed users on blocked domains" do
688 domain = "meanies.social"
689 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
690 blocker = insert(:user)
691
692 {:ok, blocker} = User.follow(blocker, domain_user)
693 {:ok, blocker} = User.block_domain(blocker, domain)
694
695 assert User.following?(blocker, domain_user)
696 assert User.blocks_domain?(blocker, domain_user)
697 refute User.blocks?(blocker, domain_user)
698
699 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
700 activity = insert(:note_activity, %{note: note})
701
702 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
703
704 assert activity in activities
705
706 # And check that if the guy we DO follow boosts someone else from their domain,
707 # that should be hidden
708 another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"})
709 bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}})
710 bad_activity = insert(:note_activity, %{note: bad_note})
711 {:ok, repeat_activity} = CommonAPI.repeat(bad_activity.id, domain_user)
712
713 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
714
715 refute repeat_activity in activities
716 end
717
718 test "doesn't return muted activities" do
719 activity_one = insert(:note_activity)
720 activity_two = insert(:note_activity)
721 activity_three = insert(:note_activity)
722 user = insert(:user)
723 booster = insert(:user)
724
725 activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
726 {:ok, _user_relationships} = User.mute(user, activity_one_actor)
727
728 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
729
730 assert Enum.member?(activities, activity_two)
731 assert Enum.member?(activities, activity_three)
732 refute Enum.member?(activities, activity_one)
733
734 # Calling with 'with_muted' will deliver muted activities, too.
735 activities =
736 ActivityPub.fetch_activities([], %{
737 muting_user: user,
738 with_muted: true,
739 skip_preload: true
740 })
741
742 assert Enum.member?(activities, activity_two)
743 assert Enum.member?(activities, activity_three)
744 assert Enum.member?(activities, activity_one)
745
746 {:ok, _user_mute} = User.unmute(user, activity_one_actor)
747
748 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
749
750 assert Enum.member?(activities, activity_two)
751 assert Enum.member?(activities, activity_three)
752 assert Enum.member?(activities, activity_one)
753
754 activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
755 {:ok, _user_relationships} = User.mute(user, activity_three_actor)
756 {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
757 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
758 activity_three = Activity.get_by_id(activity_three.id)
759
760 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
761
762 assert Enum.member?(activities, activity_two)
763 refute Enum.member?(activities, activity_three)
764 refute Enum.member?(activities, boost_activity)
765 assert Enum.member?(activities, activity_one)
766
767 activities = ActivityPub.fetch_activities([], %{muting_user: nil, skip_preload: true})
768
769 assert Enum.member?(activities, activity_two)
770 assert Enum.member?(activities, activity_three)
771 assert Enum.member?(activities, boost_activity)
772 assert Enum.member?(activities, activity_one)
773 end
774
775 test "doesn't return thread muted activities" do
776 user = insert(:user)
777 _activity_one = insert(:note_activity)
778 note_two = insert(:note, data: %{"context" => "suya.."})
779 activity_two = insert(:note_activity, note: note_two)
780
781 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
782
783 assert [_activity_one] = ActivityPub.fetch_activities([], %{muting_user: user})
784 end
785
786 test "returns thread muted activities when with_muted is set" do
787 user = insert(:user)
788 _activity_one = insert(:note_activity)
789 note_two = insert(:note, data: %{"context" => "suya.."})
790 activity_two = insert(:note_activity, note: note_two)
791
792 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
793
794 assert [_activity_two, _activity_one] =
795 ActivityPub.fetch_activities([], %{muting_user: user, with_muted: true})
796 end
797
798 test "does include announces on request" do
799 activity_three = insert(:note_activity)
800 user = insert(:user)
801 booster = insert(:user)
802
803 {:ok, user} = User.follow(user, booster)
804
805 {:ok, announce} = CommonAPI.repeat(activity_three.id, booster)
806
807 [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])
808
809 assert announce_activity.id == announce.id
810 end
811
812 test "excludes reblogs on request" do
813 user = insert(:user)
814 {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
815 {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
816
817 [activity] = ActivityPub.fetch_user_activities(user, nil, %{exclude_reblogs: true})
818
819 assert activity == expected_activity
820 end
821
822 describe "irreversible filters" do
823 setup do
824 user = insert(:user)
825 user_two = insert(:user)
826
827 insert(:filter, user: user_two, phrase: "cofe", hide: true)
828 insert(:filter, user: user_two, phrase: "ok boomer", hide: true)
829 insert(:filter, user: user_two, phrase: "test", hide: false)
830
831 params = %{
832 type: ["Create", "Announce"],
833 user: user_two
834 }
835
836 {:ok, %{user: user, user_two: user_two, params: params}}
837 end
838
839 test "it returns statuses if they don't contain exact filter words", %{
840 user: user,
841 params: params
842 } do
843 {:ok, _} = CommonAPI.post(user, %{status: "hey"})
844 {:ok, _} = CommonAPI.post(user, %{status: "got cofefe?"})
845 {:ok, _} = CommonAPI.post(user, %{status: "I am not a boomer"})
846 {:ok, _} = CommonAPI.post(user, %{status: "ok boomers"})
847 {:ok, _} = CommonAPI.post(user, %{status: "ccofee is not a word"})
848 {:ok, _} = CommonAPI.post(user, %{status: "this is a test"})
849
850 activities = ActivityPub.fetch_activities([], params)
851
852 assert Enum.count(activities) == 6
853 end
854
855 test "it does not filter user's own statuses", %{user_two: user_two, params: params} do
856 {:ok, _} = CommonAPI.post(user_two, %{status: "Give me some cofe!"})
857 {:ok, _} = CommonAPI.post(user_two, %{status: "ok boomer"})
858
859 activities = ActivityPub.fetch_activities([], params)
860
861 assert Enum.count(activities) == 2
862 end
863
864 test "it excludes statuses with filter words", %{user: user, params: params} do
865 {:ok, _} = CommonAPI.post(user, %{status: "Give me some cofe!"})
866 {:ok, _} = CommonAPI.post(user, %{status: "ok boomer"})
867 {:ok, _} = CommonAPI.post(user, %{status: "is it a cOfE?"})
868 {:ok, _} = CommonAPI.post(user, %{status: "cofe is all I need"})
869 {:ok, _} = CommonAPI.post(user, %{status: "— ok BOOMER\n"})
870
871 activities = ActivityPub.fetch_activities([], params)
872
873 assert Enum.empty?(activities)
874 end
875
876 test "it returns all statuses if user does not have any filters" do
877 another_user = insert(:user)
878 {:ok, _} = CommonAPI.post(another_user, %{status: "got cofe?"})
879 {:ok, _} = CommonAPI.post(another_user, %{status: "test!"})
880
881 activities =
882 ActivityPub.fetch_activities([], %{
883 type: ["Create", "Announce"],
884 user: another_user
885 })
886
887 assert Enum.count(activities) == 2
888 end
889 end
890
891 describe "public fetch activities" do
892 test "doesn't retrieve unlisted activities" do
893 user = insert(:user)
894
895 {:ok, _unlisted_activity} = CommonAPI.post(user, %{status: "yeah", visibility: "unlisted"})
896
897 {:ok, listed_activity} = CommonAPI.post(user, %{status: "yeah"})
898
899 [activity] = ActivityPub.fetch_public_activities()
900
901 assert activity == listed_activity
902 end
903
904 test "retrieves public activities" do
905 _activities = ActivityPub.fetch_public_activities()
906
907 %{public: public} = ActivityBuilder.public_and_non_public()
908
909 activities = ActivityPub.fetch_public_activities()
910 assert length(activities) == 1
911 assert Enum.at(activities, 0) == public
912 end
913
914 test "retrieves a maximum of 20 activities" do
915 ActivityBuilder.insert_list(10)
916 expected_activities = ActivityBuilder.insert_list(20)
917
918 activities = ActivityPub.fetch_public_activities()
919
920 assert collect_ids(activities) == collect_ids(expected_activities)
921 assert length(activities) == 20
922 end
923
924 test "retrieves ids starting from a since_id" do
925 activities = ActivityBuilder.insert_list(30)
926 expected_activities = ActivityBuilder.insert_list(10)
927 since_id = List.last(activities).id
928
929 activities = ActivityPub.fetch_public_activities(%{since_id: since_id})
930
931 assert collect_ids(activities) == collect_ids(expected_activities)
932 assert length(activities) == 10
933 end
934
935 test "retrieves ids up to max_id" do
936 ActivityBuilder.insert_list(10)
937 expected_activities = ActivityBuilder.insert_list(20)
938
939 %{id: max_id} =
940 10
941 |> ActivityBuilder.insert_list()
942 |> List.first()
943
944 activities = ActivityPub.fetch_public_activities(%{max_id: max_id})
945
946 assert length(activities) == 20
947 assert collect_ids(activities) == collect_ids(expected_activities)
948 end
949
950 test "paginates via offset/limit" do
951 _first_part_activities = ActivityBuilder.insert_list(10)
952 second_part_activities = ActivityBuilder.insert_list(10)
953
954 later_activities = ActivityBuilder.insert_list(10)
955
956 activities = ActivityPub.fetch_public_activities(%{page: "2", page_size: "20"}, :offset)
957
958 assert length(activities) == 20
959
960 assert collect_ids(activities) ==
961 collect_ids(second_part_activities) ++ collect_ids(later_activities)
962 end
963
964 test "doesn't return reblogs for users for whom reblogs have been muted" do
965 activity = insert(:note_activity)
966 user = insert(:user)
967 booster = insert(:user)
968 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
969
970 {:ok, activity} = CommonAPI.repeat(activity.id, booster)
971
972 activities = ActivityPub.fetch_activities([], %{muting_user: user})
973
974 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
975 end
976
977 test "returns reblogs for users for whom reblogs have not been muted" do
978 activity = insert(:note_activity)
979 user = insert(:user)
980 booster = insert(:user)
981 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
982 {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
983
984 {:ok, activity} = CommonAPI.repeat(activity.id, booster)
985
986 activities = ActivityPub.fetch_activities([], %{muting_user: user})
987
988 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
989 end
990 end
991
992 describe "uploading files" do
993 test "copies the file to the configured folder" do
994 file = %Plug.Upload{
995 content_type: "image/jpg",
996 path: Path.absname("test/fixtures/image.jpg"),
997 filename: "an_image.jpg"
998 }
999
1000 {:ok, %Object{} = object} = ActivityPub.upload(file)
1001 assert object.data["name"] == "an_image.jpg"
1002 end
1003
1004 test "works with base64 encoded images" do
1005 file = %{
1006 img: data_uri()
1007 }
1008
1009 {:ok, %Object{}} = ActivityPub.upload(file)
1010 end
1011 end
1012
1013 describe "fetch the latest Follow" do
1014 test "fetches the latest Follow activity" do
1015 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1016 follower = Repo.get_by(User, ap_id: activity.data["actor"])
1017 followed = Repo.get_by(User, ap_id: activity.data["object"])
1018
1019 assert activity == Utils.fetch_latest_follow(follower, followed)
1020 end
1021 end
1022
1023 describe "unfollowing" do
1024 test "it reverts unfollow activity" do
1025 follower = insert(:user)
1026 followed = insert(:user)
1027
1028 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1029
1030 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1031 assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1032 end
1033
1034 activity = Activity.get_by_id(follow_activity.id)
1035 assert activity.data["type"] == "Follow"
1036 assert activity.data["actor"] == follower.ap_id
1037
1038 assert activity.data["object"] == followed.ap_id
1039 end
1040
1041 test "creates an undo activity for the last follow" do
1042 follower = insert(:user)
1043 followed = insert(:user)
1044
1045 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1046 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1047
1048 assert activity.data["type"] == "Undo"
1049 assert activity.data["actor"] == follower.ap_id
1050
1051 embedded_object = activity.data["object"]
1052 assert is_map(embedded_object)
1053 assert embedded_object["type"] == "Follow"
1054 assert embedded_object["object"] == followed.ap_id
1055 assert embedded_object["id"] == follow_activity.data["id"]
1056 end
1057
1058 test "creates an undo activity for a pending follow request" do
1059 follower = insert(:user)
1060 followed = insert(:user, %{locked: true})
1061
1062 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1063 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1064
1065 assert activity.data["type"] == "Undo"
1066 assert activity.data["actor"] == follower.ap_id
1067
1068 embedded_object = activity.data["object"]
1069 assert is_map(embedded_object)
1070 assert embedded_object["type"] == "Follow"
1071 assert embedded_object["object"] == followed.ap_id
1072 assert embedded_object["id"] == follow_activity.data["id"]
1073 end
1074 end
1075
1076 describe "timeline post-processing" do
1077 test "it filters broken threads" do
1078 user1 = insert(:user)
1079 user2 = insert(:user)
1080 user3 = insert(:user)
1081
1082 {:ok, user1} = User.follow(user1, user3)
1083 assert User.following?(user1, user3)
1084
1085 {:ok, user2} = User.follow(user2, user3)
1086 assert User.following?(user2, user3)
1087
1088 {:ok, user3} = User.follow(user3, user2)
1089 assert User.following?(user3, user2)
1090
1091 {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"})
1092
1093 {:ok, private_activity_1} = CommonAPI.post(user3, %{status: "hi 2", visibility: "private"})
1094
1095 {:ok, private_activity_2} =
1096 CommonAPI.post(user2, %{
1097 status: "hi 3",
1098 visibility: "private",
1099 in_reply_to_status_id: private_activity_1.id
1100 })
1101
1102 {:ok, private_activity_3} =
1103 CommonAPI.post(user3, %{
1104 status: "hi 4",
1105 visibility: "private",
1106 in_reply_to_status_id: private_activity_2.id
1107 })
1108
1109 activities =
1110 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1111 |> Enum.map(fn a -> a.id end)
1112
1113 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1114
1115 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1116
1117 assert length(activities) == 3
1118
1119 activities =
1120 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{user: user1})
1121 |> Enum.map(fn a -> a.id end)
1122
1123 assert [public_activity.id, private_activity_1.id] == activities
1124 assert length(activities) == 2
1125 end
1126 end
1127
1128 describe "flag/1" do
1129 setup do
1130 reporter = insert(:user)
1131 target_account = insert(:user)
1132 content = "foobar"
1133 {:ok, activity} = CommonAPI.post(target_account, %{status: content})
1134 context = Utils.generate_context_id()
1135
1136 reporter_ap_id = reporter.ap_id
1137 target_ap_id = target_account.ap_id
1138 activity_ap_id = activity.data["id"]
1139
1140 activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1141
1142 {:ok,
1143 %{
1144 reporter: reporter,
1145 context: context,
1146 target_account: target_account,
1147 reported_activity: activity,
1148 content: content,
1149 activity_ap_id: activity_ap_id,
1150 activity_with_object: activity_with_object,
1151 reporter_ap_id: reporter_ap_id,
1152 target_ap_id: target_ap_id
1153 }}
1154 end
1155
1156 test "it can create a Flag activity",
1157 %{
1158 reporter: reporter,
1159 context: context,
1160 target_account: target_account,
1161 reported_activity: reported_activity,
1162 content: content,
1163 activity_ap_id: activity_ap_id,
1164 activity_with_object: activity_with_object,
1165 reporter_ap_id: reporter_ap_id,
1166 target_ap_id: target_ap_id
1167 } do
1168 assert {:ok, activity} =
1169 ActivityPub.flag(%{
1170 actor: reporter,
1171 context: context,
1172 account: target_account,
1173 statuses: [reported_activity],
1174 content: content
1175 })
1176
1177 note_obj = %{
1178 "type" => "Note",
1179 "id" => activity_ap_id,
1180 "content" => content,
1181 "published" => activity_with_object.object.data["published"],
1182 "actor" => AccountView.render("show.json", %{user: target_account})
1183 }
1184
1185 assert %Activity{
1186 actor: ^reporter_ap_id,
1187 data: %{
1188 "type" => "Flag",
1189 "content" => ^content,
1190 "context" => ^context,
1191 "object" => [^target_ap_id, ^note_obj]
1192 }
1193 } = activity
1194 end
1195
1196 test_with_mock "strips status data from Flag, before federating it",
1197 %{
1198 reporter: reporter,
1199 context: context,
1200 target_account: target_account,
1201 reported_activity: reported_activity,
1202 content: content
1203 },
1204 Utils,
1205 [:passthrough],
1206 [] do
1207 {:ok, activity} =
1208 ActivityPub.flag(%{
1209 actor: reporter,
1210 context: context,
1211 account: target_account,
1212 statuses: [reported_activity],
1213 content: content
1214 })
1215
1216 new_data =
1217 put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]])
1218
1219 assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1220 end
1221 end
1222
1223 test "fetch_activities/2 returns activities addressed to a list " do
1224 user = insert(:user)
1225 member = insert(:user)
1226 {:ok, list} = Pleroma.List.create("foo", user)
1227 {:ok, list} = Pleroma.List.follow(list, member)
1228
1229 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1230
1231 activity = Repo.preload(activity, :bookmark)
1232 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1233
1234 assert ActivityPub.fetch_activities([], %{user: user}) == [activity]
1235 end
1236
1237 def data_uri do
1238 File.read!("test/fixtures/avatar_data_uri")
1239 end
1240
1241 describe "fetch_activities_bounded" do
1242 test "fetches private posts for followed users" do
1243 user = insert(:user)
1244
1245 {:ok, activity} =
1246 CommonAPI.post(user, %{
1247 status: "thought I looked cute might delete later :3",
1248 visibility: "private"
1249 })
1250
1251 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1252 assert result.id == activity.id
1253 end
1254
1255 test "fetches only public posts for other users" do
1256 user = insert(:user)
1257 {:ok, activity} = CommonAPI.post(user, %{status: "#cofe", visibility: "public"})
1258
1259 {:ok, _private_activity} =
1260 CommonAPI.post(user, %{
1261 status: "why is tenshi eating a corndog so cute?",
1262 visibility: "private"
1263 })
1264
1265 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1266 assert result.id == activity.id
1267 end
1268 end
1269
1270 describe "fetch_follow_information_for_user" do
1271 test "syncronizes following/followers counters" do
1272 user =
1273 insert(:user,
1274 local: false,
1275 follower_address: "http://localhost:4001/users/fuser2/followers",
1276 following_address: "http://localhost:4001/users/fuser2/following"
1277 )
1278
1279 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1280 assert info.follower_count == 527
1281 assert info.following_count == 267
1282 end
1283
1284 test "detects hidden followers" do
1285 mock(fn env ->
1286 case env.url do
1287 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1288 %Tesla.Env{status: 403, body: ""}
1289
1290 _ ->
1291 apply(HttpRequestMock, :request, [env])
1292 end
1293 end)
1294
1295 user =
1296 insert(:user,
1297 local: false,
1298 follower_address: "http://localhost:4001/users/masto_closed/followers",
1299 following_address: "http://localhost:4001/users/masto_closed/following"
1300 )
1301
1302 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1303 assert follow_info.hide_followers == true
1304 assert follow_info.hide_follows == false
1305 end
1306
1307 test "detects hidden follows" do
1308 mock(fn env ->
1309 case env.url do
1310 "http://localhost:4001/users/masto_closed/following?page=1" ->
1311 %Tesla.Env{status: 403, body: ""}
1312
1313 _ ->
1314 apply(HttpRequestMock, :request, [env])
1315 end
1316 end)
1317
1318 user =
1319 insert(:user,
1320 local: false,
1321 follower_address: "http://localhost:4001/users/masto_closed/followers",
1322 following_address: "http://localhost:4001/users/masto_closed/following"
1323 )
1324
1325 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1326 assert follow_info.hide_followers == false
1327 assert follow_info.hide_follows == true
1328 end
1329
1330 test "detects hidden follows/followers for friendica" do
1331 user =
1332 insert(:user,
1333 local: false,
1334 follower_address: "http://localhost:8080/followers/fuser3",
1335 following_address: "http://localhost:8080/following/fuser3"
1336 )
1337
1338 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1339 assert follow_info.hide_followers == true
1340 assert follow_info.follower_count == 296
1341 assert follow_info.following_count == 32
1342 assert follow_info.hide_follows == true
1343 end
1344
1345 test "doesn't crash when follower and following counters are hidden" do
1346 mock(fn env ->
1347 case env.url do
1348 "http://localhost:4001/users/masto_hidden_counters/following" ->
1349 json(%{
1350 "@context" => "https://www.w3.org/ns/activitystreams",
1351 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1352 })
1353
1354 "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1355 %Tesla.Env{status: 403, body: ""}
1356
1357 "http://localhost:4001/users/masto_hidden_counters/followers" ->
1358 json(%{
1359 "@context" => "https://www.w3.org/ns/activitystreams",
1360 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1361 })
1362
1363 "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1364 %Tesla.Env{status: 403, body: ""}
1365 end
1366 end)
1367
1368 user =
1369 insert(:user,
1370 local: false,
1371 follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1372 following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1373 )
1374
1375 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1376
1377 assert follow_info.hide_followers == true
1378 assert follow_info.follower_count == 0
1379 assert follow_info.hide_follows == true
1380 assert follow_info.following_count == 0
1381 end
1382 end
1383
1384 describe "fetch_favourites/3" do
1385 test "returns a favourite activities sorted by adds to favorite" do
1386 user = insert(:user)
1387 other_user = insert(:user)
1388 user1 = insert(:user)
1389 user2 = insert(:user)
1390 {:ok, a1} = CommonAPI.post(user1, %{status: "bla"})
1391 {:ok, _a2} = CommonAPI.post(user2, %{status: "traps are happy"})
1392 {:ok, a3} = CommonAPI.post(user2, %{status: "Trees Are "})
1393 {:ok, a4} = CommonAPI.post(user2, %{status: "Agent Smith "})
1394 {:ok, a5} = CommonAPI.post(user1, %{status: "Red or Blue "})
1395
1396 {:ok, _} = CommonAPI.favorite(user, a4.id)
1397 {:ok, _} = CommonAPI.favorite(other_user, a3.id)
1398 {:ok, _} = CommonAPI.favorite(user, a3.id)
1399 {:ok, _} = CommonAPI.favorite(other_user, a5.id)
1400 {:ok, _} = CommonAPI.favorite(user, a5.id)
1401 {:ok, _} = CommonAPI.favorite(other_user, a4.id)
1402 {:ok, _} = CommonAPI.favorite(user, a1.id)
1403 {:ok, _} = CommonAPI.favorite(other_user, a1.id)
1404 result = ActivityPub.fetch_favourites(user)
1405
1406 assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1407
1408 result = ActivityPub.fetch_favourites(user, %{limit: 2})
1409 assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1410 end
1411 end
1412
1413 describe "Move activity" do
1414 test "create" do
1415 %{ap_id: old_ap_id} = old_user = insert(:user)
1416 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1417 follower = insert(:user)
1418 follower_move_opted_out = insert(:user, allow_following_move: false)
1419
1420 User.follow(follower, old_user)
1421 User.follow(follower_move_opted_out, old_user)
1422
1423 assert User.following?(follower, old_user)
1424 assert User.following?(follower_move_opted_out, old_user)
1425
1426 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1427
1428 assert %Activity{
1429 actor: ^old_ap_id,
1430 data: %{
1431 "actor" => ^old_ap_id,
1432 "object" => ^old_ap_id,
1433 "target" => ^new_ap_id,
1434 "type" => "Move"
1435 },
1436 local: true
1437 } = activity
1438
1439 params = %{
1440 "op" => "move_following",
1441 "origin_id" => old_user.id,
1442 "target_id" => new_user.id
1443 }
1444
1445 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1446
1447 Pleroma.Workers.BackgroundWorker.perform(params, nil)
1448
1449 refute User.following?(follower, old_user)
1450 assert User.following?(follower, new_user)
1451
1452 assert User.following?(follower_move_opted_out, old_user)
1453 refute User.following?(follower_move_opted_out, new_user)
1454
1455 activity = %Activity{activity | object: nil}
1456
1457 assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1458
1459 assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1460 end
1461
1462 test "old user must be in the new user's `also_known_as` list" do
1463 old_user = insert(:user)
1464 new_user = insert(:user)
1465
1466 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1467 ActivityPub.move(old_user, new_user)
1468 end
1469 end
1470
1471 test "doesn't retrieve replies activities with exclude_replies" do
1472 user = insert(:user)
1473
1474 {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
1475
1476 {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id})
1477
1478 [result] = ActivityPub.fetch_public_activities(%{exclude_replies: true})
1479
1480 assert result.id == activity.id
1481
1482 assert length(ActivityPub.fetch_public_activities()) == 2
1483 end
1484
1485 describe "replies filtering with public messages" do
1486 setup :public_messages
1487
1488 test "public timeline", %{users: %{u1: user}} do
1489 activities_ids =
1490 %{}
1491 |> Map.put(:type, ["Create", "Announce"])
1492 |> Map.put(:local_only, false)
1493 |> Map.put(:blocking_user, user)
1494 |> Map.put(:muting_user, user)
1495 |> Map.put(:reply_filtering_user, user)
1496 |> ActivityPub.fetch_public_activities()
1497 |> Enum.map(& &1.id)
1498
1499 assert length(activities_ids) == 16
1500 end
1501
1502 test "public timeline with reply_visibility `following`", %{
1503 users: %{u1: user},
1504 u1: u1,
1505 u2: u2,
1506 u3: u3,
1507 u4: u4,
1508 activities: activities
1509 } do
1510 activities_ids =
1511 %{}
1512 |> Map.put(:type, ["Create", "Announce"])
1513 |> Map.put(:local_only, false)
1514 |> Map.put(:blocking_user, user)
1515 |> Map.put(:muting_user, user)
1516 |> Map.put(:reply_visibility, "following")
1517 |> Map.put(:reply_filtering_user, user)
1518 |> ActivityPub.fetch_public_activities()
1519 |> Enum.map(& &1.id)
1520
1521 assert length(activities_ids) == 14
1522
1523 visible_ids =
1524 Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
1525
1526 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1527 end
1528
1529 test "public timeline with reply_visibility `self`", %{
1530 users: %{u1: user},
1531 u1: u1,
1532 u2: u2,
1533 u3: u3,
1534 u4: u4,
1535 activities: activities
1536 } do
1537 activities_ids =
1538 %{}
1539 |> Map.put(:type, ["Create", "Announce"])
1540 |> Map.put(:local_only, false)
1541 |> Map.put(:blocking_user, user)
1542 |> Map.put(:muting_user, user)
1543 |> Map.put(:reply_visibility, "self")
1544 |> Map.put(:reply_filtering_user, user)
1545 |> ActivityPub.fetch_public_activities()
1546 |> Enum.map(& &1.id)
1547
1548 assert length(activities_ids) == 10
1549 visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
1550 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1551 end
1552
1553 test "home timeline", %{
1554 users: %{u1: user},
1555 activities: activities,
1556 u1: u1,
1557 u2: u2,
1558 u3: u3,
1559 u4: u4
1560 } do
1561 params =
1562 %{}
1563 |> Map.put(:type, ["Create", "Announce"])
1564 |> Map.put(:blocking_user, user)
1565 |> Map.put(:muting_user, user)
1566 |> Map.put(:user, user)
1567 |> Map.put(:reply_filtering_user, user)
1568
1569 activities_ids =
1570 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1571 |> Enum.map(& &1.id)
1572
1573 assert length(activities_ids) == 13
1574
1575 visible_ids =
1576 Map.values(u1) ++
1577 Map.values(u3) ++
1578 [
1579 activities[:a1],
1580 activities[:a2],
1581 activities[:a4],
1582 u2[:r1],
1583 u2[:r3],
1584 u4[:r1],
1585 u4[:r2]
1586 ]
1587
1588 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1589 end
1590
1591 test "home timeline with reply_visibility `following`", %{
1592 users: %{u1: user},
1593 activities: activities,
1594 u1: u1,
1595 u2: u2,
1596 u3: u3,
1597 u4: u4
1598 } do
1599 params =
1600 %{}
1601 |> Map.put(:type, ["Create", "Announce"])
1602 |> Map.put(:blocking_user, user)
1603 |> Map.put(:muting_user, user)
1604 |> Map.put(:user, user)
1605 |> Map.put(:reply_visibility, "following")
1606 |> Map.put(:reply_filtering_user, user)
1607
1608 activities_ids =
1609 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1610 |> Enum.map(& &1.id)
1611
1612 assert length(activities_ids) == 11
1613
1614 visible_ids =
1615 Map.values(u1) ++
1616 [
1617 activities[:a1],
1618 activities[:a2],
1619 activities[:a4],
1620 u2[:r1],
1621 u2[:r3],
1622 u3[:r1],
1623 u4[:r1],
1624 u4[:r2]
1625 ]
1626
1627 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1628 end
1629
1630 test "home timeline with reply_visibility `self`", %{
1631 users: %{u1: user},
1632 activities: activities,
1633 u1: u1,
1634 u2: u2,
1635 u3: u3,
1636 u4: u4
1637 } do
1638 params =
1639 %{}
1640 |> Map.put(:type, ["Create", "Announce"])
1641 |> Map.put(:blocking_user, user)
1642 |> Map.put(:muting_user, user)
1643 |> Map.put(:user, user)
1644 |> Map.put(:reply_visibility, "self")
1645 |> Map.put(:reply_filtering_user, user)
1646
1647 activities_ids =
1648 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1649 |> Enum.map(& &1.id)
1650
1651 assert length(activities_ids) == 9
1652
1653 visible_ids =
1654 Map.values(u1) ++
1655 [
1656 activities[:a1],
1657 activities[:a2],
1658 activities[:a4],
1659 u2[:r1],
1660 u3[:r1],
1661 u4[:r1]
1662 ]
1663
1664 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1665 end
1666
1667 test "filtering out announces where the user is the actor of the announced message" do
1668 user = insert(:user)
1669 other_user = insert(:user)
1670 third_user = insert(:user)
1671 User.follow(user, other_user)
1672
1673 {:ok, post} = CommonAPI.post(user, %{status: "yo"})
1674 {:ok, other_post} = CommonAPI.post(third_user, %{status: "yo"})
1675 {:ok, _announce} = CommonAPI.repeat(post.id, other_user)
1676 {:ok, _announce} = CommonAPI.repeat(post.id, third_user)
1677 {:ok, announce} = CommonAPI.repeat(other_post.id, other_user)
1678
1679 params = %{
1680 type: ["Announce"]
1681 }
1682
1683 results =
1684 [user.ap_id | User.following(user)]
1685 |> ActivityPub.fetch_activities(params)
1686
1687 assert length(results) == 3
1688
1689 params = %{
1690 type: ["Announce"],
1691 announce_filtering_user: user
1692 }
1693
1694 [result] =
1695 [user.ap_id | User.following(user)]
1696 |> ActivityPub.fetch_activities(params)
1697
1698 assert result.id == announce.id
1699 end
1700 end
1701
1702 describe "replies filtering with private messages" do
1703 setup :private_messages
1704
1705 test "public timeline", %{users: %{u1: user}} do
1706 activities_ids =
1707 %{}
1708 |> Map.put(:type, ["Create", "Announce"])
1709 |> Map.put(:local_only, false)
1710 |> Map.put(:blocking_user, user)
1711 |> Map.put(:muting_user, user)
1712 |> Map.put(:user, user)
1713 |> ActivityPub.fetch_public_activities()
1714 |> Enum.map(& &1.id)
1715
1716 assert activities_ids == []
1717 end
1718
1719 test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
1720 activities_ids =
1721 %{}
1722 |> Map.put(:type, ["Create", "Announce"])
1723 |> Map.put(:local_only, false)
1724 |> Map.put(:blocking_user, user)
1725 |> Map.put(:muting_user, user)
1726 |> Map.put(:reply_visibility, "following")
1727 |> Map.put(:reply_filtering_user, user)
1728 |> Map.put(:user, user)
1729 |> ActivityPub.fetch_public_activities()
1730 |> Enum.map(& &1.id)
1731
1732 assert activities_ids == []
1733 end
1734
1735 test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
1736 activities_ids =
1737 %{}
1738 |> Map.put(:type, ["Create", "Announce"])
1739 |> Map.put(:local_only, false)
1740 |> Map.put(:blocking_user, user)
1741 |> Map.put(:muting_user, user)
1742 |> Map.put(:reply_visibility, "self")
1743 |> Map.put(:reply_filtering_user, user)
1744 |> Map.put(:user, user)
1745 |> ActivityPub.fetch_public_activities()
1746 |> Enum.map(& &1.id)
1747
1748 assert activities_ids == []
1749 end
1750
1751 test "home timeline", %{users: %{u1: user}} do
1752 params =
1753 %{}
1754 |> Map.put(:type, ["Create", "Announce"])
1755 |> Map.put(:blocking_user, user)
1756 |> Map.put(:muting_user, user)
1757 |> Map.put(:user, user)
1758
1759 activities_ids =
1760 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1761 |> Enum.map(& &1.id)
1762
1763 assert length(activities_ids) == 12
1764 end
1765
1766 test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
1767 params =
1768 %{}
1769 |> Map.put(:type, ["Create", "Announce"])
1770 |> Map.put(:blocking_user, user)
1771 |> Map.put(:muting_user, user)
1772 |> Map.put(:user, user)
1773 |> Map.put(:reply_visibility, "following")
1774 |> Map.put(:reply_filtering_user, user)
1775
1776 activities_ids =
1777 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1778 |> Enum.map(& &1.id)
1779
1780 assert length(activities_ids) == 12
1781 end
1782
1783 test "home timeline with default reply_visibility `self`", %{
1784 users: %{u1: user},
1785 activities: activities,
1786 u1: u1,
1787 u2: u2,
1788 u3: u3,
1789 u4: u4
1790 } do
1791 params =
1792 %{}
1793 |> Map.put(:type, ["Create", "Announce"])
1794 |> Map.put(:blocking_user, user)
1795 |> Map.put(:muting_user, user)
1796 |> Map.put(:user, user)
1797 |> Map.put(:reply_visibility, "self")
1798 |> Map.put(:reply_filtering_user, user)
1799
1800 activities_ids =
1801 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1802 |> Enum.map(& &1.id)
1803
1804 assert length(activities_ids) == 10
1805
1806 visible_ids =
1807 Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
1808
1809 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1810 end
1811 end
1812
1813 defp public_messages(_) do
1814 [u1, u2, u3, u4] = insert_list(4, :user)
1815 {:ok, u1} = User.follow(u1, u2)
1816 {:ok, u2} = User.follow(u2, u1)
1817 {:ok, u1} = User.follow(u1, u4)
1818 {:ok, u4} = User.follow(u4, u1)
1819
1820 {:ok, u2} = User.follow(u2, u3)
1821 {:ok, u3} = User.follow(u3, u2)
1822
1823 {:ok, a1} = CommonAPI.post(u1, %{status: "Status"})
1824
1825 {:ok, r1_1} =
1826 CommonAPI.post(u2, %{
1827 status: "@#{u1.nickname} reply from u2 to u1",
1828 in_reply_to_status_id: a1.id
1829 })
1830
1831 {:ok, r1_2} =
1832 CommonAPI.post(u3, %{
1833 status: "@#{u1.nickname} reply from u3 to u1",
1834 in_reply_to_status_id: a1.id
1835 })
1836
1837 {:ok, r1_3} =
1838 CommonAPI.post(u4, %{
1839 status: "@#{u1.nickname} reply from u4 to u1",
1840 in_reply_to_status_id: a1.id
1841 })
1842
1843 {:ok, a2} = CommonAPI.post(u2, %{status: "Status"})
1844
1845 {:ok, r2_1} =
1846 CommonAPI.post(u1, %{
1847 status: "@#{u2.nickname} reply from u1 to u2",
1848 in_reply_to_status_id: a2.id
1849 })
1850
1851 {:ok, r2_2} =
1852 CommonAPI.post(u3, %{
1853 status: "@#{u2.nickname} reply from u3 to u2",
1854 in_reply_to_status_id: a2.id
1855 })
1856
1857 {:ok, r2_3} =
1858 CommonAPI.post(u4, %{
1859 status: "@#{u2.nickname} reply from u4 to u2",
1860 in_reply_to_status_id: a2.id
1861 })
1862
1863 {:ok, a3} = CommonAPI.post(u3, %{status: "Status"})
1864
1865 {:ok, r3_1} =
1866 CommonAPI.post(u1, %{
1867 status: "@#{u3.nickname} reply from u1 to u3",
1868 in_reply_to_status_id: a3.id
1869 })
1870
1871 {:ok, r3_2} =
1872 CommonAPI.post(u2, %{
1873 status: "@#{u3.nickname} reply from u2 to u3",
1874 in_reply_to_status_id: a3.id
1875 })
1876
1877 {:ok, r3_3} =
1878 CommonAPI.post(u4, %{
1879 status: "@#{u3.nickname} reply from u4 to u3",
1880 in_reply_to_status_id: a3.id
1881 })
1882
1883 {:ok, a4} = CommonAPI.post(u4, %{status: "Status"})
1884
1885 {:ok, r4_1} =
1886 CommonAPI.post(u1, %{
1887 status: "@#{u4.nickname} reply from u1 to u4",
1888 in_reply_to_status_id: a4.id
1889 })
1890
1891 {:ok, r4_2} =
1892 CommonAPI.post(u2, %{
1893 status: "@#{u4.nickname} reply from u2 to u4",
1894 in_reply_to_status_id: a4.id
1895 })
1896
1897 {:ok, r4_3} =
1898 CommonAPI.post(u3, %{
1899 status: "@#{u4.nickname} reply from u3 to u4",
1900 in_reply_to_status_id: a4.id
1901 })
1902
1903 {:ok,
1904 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
1905 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
1906 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
1907 u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
1908 u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
1909 u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
1910 end
1911
1912 defp private_messages(_) do
1913 [u1, u2, u3, u4] = insert_list(4, :user)
1914 {:ok, u1} = User.follow(u1, u2)
1915 {:ok, u2} = User.follow(u2, u1)
1916 {:ok, u1} = User.follow(u1, u3)
1917 {:ok, u3} = User.follow(u3, u1)
1918 {:ok, u1} = User.follow(u1, u4)
1919 {:ok, u4} = User.follow(u4, u1)
1920
1921 {:ok, u2} = User.follow(u2, u3)
1922 {:ok, u3} = User.follow(u3, u2)
1923
1924 {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"})
1925
1926 {:ok, r1_1} =
1927 CommonAPI.post(u2, %{
1928 status: "@#{u1.nickname} reply from u2 to u1",
1929 in_reply_to_status_id: a1.id,
1930 visibility: "private"
1931 })
1932
1933 {:ok, r1_2} =
1934 CommonAPI.post(u3, %{
1935 status: "@#{u1.nickname} reply from u3 to u1",
1936 in_reply_to_status_id: a1.id,
1937 visibility: "private"
1938 })
1939
1940 {:ok, r1_3} =
1941 CommonAPI.post(u4, %{
1942 status: "@#{u1.nickname} reply from u4 to u1",
1943 in_reply_to_status_id: a1.id,
1944 visibility: "private"
1945 })
1946
1947 {:ok, a2} = CommonAPI.post(u2, %{status: "Status", visibility: "private"})
1948
1949 {:ok, r2_1} =
1950 CommonAPI.post(u1, %{
1951 status: "@#{u2.nickname} reply from u1 to u2",
1952 in_reply_to_status_id: a2.id,
1953 visibility: "private"
1954 })
1955
1956 {:ok, r2_2} =
1957 CommonAPI.post(u3, %{
1958 status: "@#{u2.nickname} reply from u3 to u2",
1959 in_reply_to_status_id: a2.id,
1960 visibility: "private"
1961 })
1962
1963 {:ok, a3} = CommonAPI.post(u3, %{status: "Status", visibility: "private"})
1964
1965 {:ok, r3_1} =
1966 CommonAPI.post(u1, %{
1967 status: "@#{u3.nickname} reply from u1 to u3",
1968 in_reply_to_status_id: a3.id,
1969 visibility: "private"
1970 })
1971
1972 {:ok, r3_2} =
1973 CommonAPI.post(u2, %{
1974 status: "@#{u3.nickname} reply from u2 to u3",
1975 in_reply_to_status_id: a3.id,
1976 visibility: "private"
1977 })
1978
1979 {:ok, a4} = CommonAPI.post(u4, %{status: "Status", visibility: "private"})
1980
1981 {:ok, r4_1} =
1982 CommonAPI.post(u1, %{
1983 status: "@#{u4.nickname} reply from u1 to u4",
1984 in_reply_to_status_id: a4.id,
1985 visibility: "private"
1986 })
1987
1988 {:ok,
1989 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
1990 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
1991 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
1992 u2: %{r1: r2_1.id, r2: r2_2.id},
1993 u3: %{r1: r3_1.id, r2: r3_2.id},
1994 u4: %{r1: r4_1.id}}
1995 end
1996
1997 describe "maybe_update_follow_information/1" do
1998 setup do
1999 clear_config([:instance, :external_user_synchronization], true)
2000
2001 user = %{
2002 local: false,
2003 ap_id: "https://gensokyo.2hu/users/raymoo",
2004 following_address: "https://gensokyo.2hu/users/following",
2005 follower_address: "https://gensokyo.2hu/users/followers",
2006 type: "Person"
2007 }
2008
2009 %{user: user}
2010 end
2011
2012 test "logs an error when it can't fetch the info", %{user: user} do
2013 assert capture_log(fn ->
2014 ActivityPub.maybe_update_follow_information(user)
2015 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2016 end
2017
2018 test "just returns the input if the user type is Application", %{
2019 user: user
2020 } do
2021 user =
2022 user
2023 |> Map.put(:type, "Application")
2024
2025 refute capture_log(fn ->
2026 assert ^user = ActivityPub.maybe_update_follow_information(user)
2027 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2028 end
2029
2030 test "it just returns the input if the user has no following/follower addresses", %{
2031 user: user
2032 } do
2033 user =
2034 user
2035 |> Map.put(:following_address, nil)
2036 |> Map.put(:follower_address, nil)
2037
2038 refute capture_log(fn ->
2039 assert ^user = ActivityPub.maybe_update_follow_information(user)
2040 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2041 end
2042 end
2043
2044 describe "global activity expiration" do
2045 setup do: clear_config([:mrf, :policies])
2046
2047 test "creates an activity expiration for local Create activities" do
2048 Pleroma.Config.put(
2049 [:mrf, :policies],
2050 Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy
2051 )
2052
2053 {:ok, %{id: id_create}} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
2054 {:ok, _follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
2055
2056 assert [%{activity_id: ^id_create}] = Pleroma.ActivityExpiration |> Repo.all()
2057 end
2058 end
2059 end