Merge remote-tracking branch 'remotes/origin/develop' into feature/object-hashtags...
[akkoma] / test / pleroma / web / activity_pub / activity_pub_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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
194 test "works for guppe actors" do
195 user_id = "https://gup.pe/u/bernie2020"
196
197 Tesla.Mock.mock(fn
198 %{method: :get, url: ^user_id} ->
199 %Tesla.Env{
200 status: 200,
201 body: File.read!("test/fixtures/guppe-actor.json"),
202 headers: [{"content-type", "application/activity+json"}]
203 }
204 end)
205
206 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
207
208 assert user.name == "Bernie2020 group"
209 assert user.actor_type == "Group"
210 end
211 end
212
213 test "it fetches the appropriate tag-restricted posts" do
214 user = insert(:user)
215
216 {:ok, status_one} = CommonAPI.post(user, %{status: ". #test"})
217 {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
218 {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
219
220 for new_timeline_enabled <- [true, false] do
221 clear_config([:instance, :improved_hashtag_timeline], new_timeline_enabled)
222
223 fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
224
225 fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]})
226
227 fetch_three =
228 ActivityPub.fetch_activities([], %{
229 type: "Create",
230 tag: ["test", "essais"],
231 tag_reject: ["reject"]
232 })
233
234 fetch_four =
235 ActivityPub.fetch_activities([], %{
236 type: "Create",
237 tag: ["test"],
238 tag_all: ["test", "reject"]
239 })
240
241 [fetch_one, fetch_two, fetch_three, fetch_four] =
242 Enum.map([fetch_one, fetch_two, fetch_three, fetch_four], fn statuses ->
243 Enum.map(statuses, fn s -> Repo.preload(s, object: :hashtags) end)
244 end)
245
246 assert fetch_one == [status_one, status_three]
247 assert fetch_two == [status_one, status_two, status_three]
248 assert fetch_three == [status_one, status_two]
249 assert fetch_four == [status_three]
250 end
251 end
252
253 describe "insertion" do
254 test "drops activities beyond a certain limit" do
255 limit = Config.get([:instance, :remote_limit])
256
257 random_text =
258 :crypto.strong_rand_bytes(limit + 1)
259 |> Base.encode64()
260 |> binary_part(0, limit + 1)
261
262 data = %{
263 "ok" => true,
264 "object" => %{
265 "content" => random_text
266 }
267 }
268
269 assert {:error, :remote_limit} = ActivityPub.insert(data)
270 end
271
272 test "doesn't drop activities with content being null" do
273 user = insert(:user)
274
275 data = %{
276 "actor" => user.ap_id,
277 "to" => [],
278 "object" => %{
279 "actor" => user.ap_id,
280 "to" => [],
281 "type" => "Note",
282 "content" => nil
283 }
284 }
285
286 assert {:ok, _} = ActivityPub.insert(data)
287 end
288
289 test "returns the activity if one with the same id is already in" do
290 activity = insert(:note_activity)
291 {:ok, new_activity} = ActivityPub.insert(activity.data)
292
293 assert activity.id == new_activity.id
294 end
295
296 test "inserts a given map into the activity database, giving it an id if it has none." do
297 user = insert(:user)
298
299 data = %{
300 "actor" => user.ap_id,
301 "to" => [],
302 "object" => %{
303 "actor" => user.ap_id,
304 "to" => [],
305 "type" => "Note",
306 "content" => "hey"
307 }
308 }
309
310 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
311 assert activity.data["ok"] == data["ok"]
312 assert is_binary(activity.data["id"])
313
314 given_id = "bla"
315
316 data = %{
317 "id" => given_id,
318 "actor" => user.ap_id,
319 "to" => [],
320 "context" => "blabla",
321 "object" => %{
322 "actor" => user.ap_id,
323 "to" => [],
324 "type" => "Note",
325 "content" => "hey"
326 }
327 }
328
329 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
330 assert activity.data["ok"] == data["ok"]
331 assert activity.data["id"] == given_id
332 assert activity.data["context"] == "blabla"
333 assert activity.data["context_id"]
334 end
335
336 test "adds a context when none is there" do
337 user = insert(:user)
338
339 data = %{
340 "actor" => user.ap_id,
341 "to" => [],
342 "object" => %{
343 "actor" => user.ap_id,
344 "to" => [],
345 "type" => "Note",
346 "content" => "hey"
347 }
348 }
349
350 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
351 object = Pleroma.Object.normalize(activity, fetch: false)
352
353 assert is_binary(activity.data["context"])
354 assert is_binary(object.data["context"])
355 assert activity.data["context_id"]
356 assert object.data["context_id"]
357 end
358
359 test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
360 user = insert(:user)
361
362 data = %{
363 "actor" => user.ap_id,
364 "to" => [],
365 "object" => %{
366 "actor" => user.ap_id,
367 "to" => [],
368 "type" => "Note",
369 "content" => "hey"
370 }
371 }
372
373 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
374 assert object = Object.normalize(activity, fetch: false)
375 assert is_binary(object.data["id"])
376 end
377 end
378
379 describe "listen activities" do
380 test "does not increase user note count" do
381 user = insert(:user)
382
383 {:ok, activity} =
384 ActivityPub.listen(%{
385 to: ["https://www.w3.org/ns/activitystreams#Public"],
386 actor: user,
387 context: "",
388 object: %{
389 "actor" => user.ap_id,
390 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
391 "artist" => "lain",
392 "title" => "lain radio episode 1",
393 "length" => 180_000,
394 "type" => "Audio"
395 }
396 })
397
398 assert activity.actor == user.ap_id
399
400 user = User.get_cached_by_id(user.id)
401 assert user.note_count == 0
402 end
403
404 test "can be fetched into a timeline" do
405 _listen_activity_1 = insert(:listen)
406 _listen_activity_2 = insert(:listen)
407 _listen_activity_3 = insert(:listen)
408
409 timeline = ActivityPub.fetch_activities([], %{type: ["Listen"]})
410
411 assert length(timeline) == 3
412 end
413 end
414
415 describe "create activities" do
416 setup do
417 [user: insert(:user)]
418 end
419
420 test "it reverts create", %{user: user} do
421 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
422 assert {:error, :reverted} =
423 ActivityPub.create(%{
424 to: ["user1", "user2"],
425 actor: user,
426 context: "",
427 object: %{
428 "to" => ["user1", "user2"],
429 "type" => "Note",
430 "content" => "testing"
431 }
432 })
433 end
434
435 assert Repo.aggregate(Activity, :count, :id) == 0
436 assert Repo.aggregate(Object, :count, :id) == 0
437 end
438
439 test "creates activity if expiration is not configured and expires_at is not passed", %{
440 user: user
441 } do
442 clear_config([Pleroma.Workers.PurgeExpiredActivity, :enabled], false)
443
444 assert {:ok, _} =
445 ActivityPub.create(%{
446 to: ["user1", "user2"],
447 actor: user,
448 context: "",
449 object: %{
450 "to" => ["user1", "user2"],
451 "type" => "Note",
452 "content" => "testing"
453 }
454 })
455 end
456
457 test "rejects activity if expires_at present but expiration is not configured", %{user: user} do
458 clear_config([Pleroma.Workers.PurgeExpiredActivity, :enabled], false)
459
460 assert {:error, :expired_activities_disabled} =
461 ActivityPub.create(%{
462 to: ["user1", "user2"],
463 actor: user,
464 context: "",
465 object: %{
466 "to" => ["user1", "user2"],
467 "type" => "Note",
468 "content" => "testing"
469 },
470 additional: %{
471 "expires_at" => DateTime.utc_now()
472 }
473 })
474
475 assert Repo.aggregate(Activity, :count, :id) == 0
476 assert Repo.aggregate(Object, :count, :id) == 0
477 end
478
479 test "removes doubled 'to' recipients", %{user: user} do
480 {:ok, activity} =
481 ActivityPub.create(%{
482 to: ["user1", "user1", "user2"],
483 actor: user,
484 context: "",
485 object: %{
486 "to" => ["user1", "user1", "user2"],
487 "type" => "Note",
488 "content" => "testing"
489 }
490 })
491
492 assert activity.data["to"] == ["user1", "user2"]
493 assert activity.actor == user.ap_id
494 assert activity.recipients == ["user1", "user2", user.ap_id]
495 end
496
497 test "increases user note count only for public activities", %{user: user} do
498 {:ok, _} =
499 CommonAPI.post(User.get_cached_by_id(user.id), %{
500 status: "1",
501 visibility: "public"
502 })
503
504 {:ok, _} =
505 CommonAPI.post(User.get_cached_by_id(user.id), %{
506 status: "2",
507 visibility: "unlisted"
508 })
509
510 {:ok, _} =
511 CommonAPI.post(User.get_cached_by_id(user.id), %{
512 status: "2",
513 visibility: "private"
514 })
515
516 {:ok, _} =
517 CommonAPI.post(User.get_cached_by_id(user.id), %{
518 status: "3",
519 visibility: "direct"
520 })
521
522 user = User.get_cached_by_id(user.id)
523 assert user.note_count == 2
524 end
525
526 test "increases replies count", %{user: user} do
527 user2 = insert(:user)
528
529 {:ok, activity} = CommonAPI.post(user, %{status: "1", visibility: "public"})
530 ap_id = activity.data["id"]
531 reply_data = %{status: "1", in_reply_to_status_id: activity.id}
532
533 # public
534 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "public"))
535 assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
536 assert object.data["repliesCount"] == 1
537
538 # unlisted
539 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "unlisted"))
540 assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
541 assert object.data["repliesCount"] == 2
542
543 # private
544 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "private"))
545 assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
546 assert object.data["repliesCount"] == 2
547
548 # direct
549 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "direct"))
550 assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
551 assert object.data["repliesCount"] == 2
552 end
553 end
554
555 describe "fetch activities for recipients" do
556 test "retrieve the activities for certain recipients" do
557 {:ok, activity_one} = ActivityBuilder.insert(%{"to" => ["someone"]})
558 {:ok, activity_two} = ActivityBuilder.insert(%{"to" => ["someone_else"]})
559 {:ok, _activity_three} = ActivityBuilder.insert(%{"to" => ["noone"]})
560
561 activities = ActivityPub.fetch_activities(["someone", "someone_else"])
562 assert length(activities) == 2
563 assert activities == [activity_one, activity_two]
564 end
565 end
566
567 describe "fetch activities in context" do
568 test "retrieves activities that have a given context" do
569 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
570 {:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
571 {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
572 {:ok, _activity_four} = ActivityBuilder.insert(%{"type" => "Announce", "context" => "2hu"})
573 activity_five = insert(:note_activity)
574 user = insert(:user)
575
576 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})
577
578 activities = ActivityPub.fetch_activities_for_context("2hu", %{blocking_user: user})
579 assert activities == [activity_two, activity]
580 end
581
582 test "doesn't return activities with filtered words" do
583 user = insert(:user)
584 user_two = insert(:user)
585 insert(:filter, user: user, phrase: "test", hide: true)
586
587 {:ok, %{id: id1, data: %{"context" => context}}} = CommonAPI.post(user, %{status: "1"})
588
589 {:ok, %{id: id2}} = CommonAPI.post(user_two, %{status: "2", in_reply_to_status_id: id1})
590
591 {:ok, %{id: id3} = user_activity} =
592 CommonAPI.post(user, %{status: "3 test?", in_reply_to_status_id: id2})
593
594 {:ok, %{id: id4} = filtered_activity} =
595 CommonAPI.post(user_two, %{status: "4 test!", in_reply_to_status_id: id3})
596
597 {:ok, _} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
598
599 activities =
600 context
601 |> ActivityPub.fetch_activities_for_context(%{user: user})
602 |> Enum.map(& &1.id)
603
604 assert length(activities) == 4
605 assert user_activity.id in activities
606 refute filtered_activity.id in activities
607 end
608 end
609
610 test "doesn't return blocked activities" do
611 activity_one = insert(:note_activity)
612 activity_two = insert(:note_activity)
613 activity_three = insert(:note_activity)
614 user = insert(:user)
615 booster = insert(:user)
616 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
617
618 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
619
620 assert Enum.member?(activities, activity_two)
621 assert Enum.member?(activities, activity_three)
622 refute Enum.member?(activities, activity_one)
623
624 {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
625
626 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
627
628 assert Enum.member?(activities, activity_two)
629 assert Enum.member?(activities, activity_three)
630 assert Enum.member?(activities, activity_one)
631
632 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})
633 {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
634 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
635 activity_three = Activity.get_by_id(activity_three.id)
636
637 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
638
639 assert Enum.member?(activities, activity_two)
640 refute Enum.member?(activities, activity_three)
641 refute Enum.member?(activities, boost_activity)
642 assert Enum.member?(activities, activity_one)
643
644 activities = ActivityPub.fetch_activities([], %{blocking_user: nil, skip_preload: true})
645
646 assert Enum.member?(activities, activity_two)
647 assert Enum.member?(activities, activity_three)
648 assert Enum.member?(activities, boost_activity)
649 assert Enum.member?(activities, activity_one)
650 end
651
652 test "doesn't return transitive interactions concerning blocked users" do
653 blocker = insert(:user)
654 blockee = insert(:user)
655 friend = insert(:user)
656
657 {:ok, _user_relationship} = User.block(blocker, blockee)
658
659 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
660
661 {:ok, activity_two} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
662
663 {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
664
665 {:ok, activity_four} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
666
667 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker})
668
669 assert Enum.member?(activities, activity_one)
670 refute Enum.member?(activities, activity_two)
671 refute Enum.member?(activities, activity_three)
672 refute Enum.member?(activities, activity_four)
673 end
674
675 test "doesn't return announce activities with blocked users in 'to'" do
676 blocker = insert(:user)
677 blockee = insert(:user)
678 friend = insert(:user)
679
680 {:ok, _user_relationship} = User.block(blocker, blockee)
681
682 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
683
684 {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
685
686 {:ok, activity_three} = CommonAPI.repeat(activity_two.id, friend)
687
688 activities =
689 ActivityPub.fetch_activities([], %{blocking_user: blocker})
690 |> Enum.map(fn act -> act.id end)
691
692 assert Enum.member?(activities, activity_one.id)
693 refute Enum.member?(activities, activity_two.id)
694 refute Enum.member?(activities, activity_three.id)
695 end
696
697 test "doesn't return announce activities with blocked users in 'cc'" do
698 blocker = insert(:user)
699 blockee = insert(:user)
700 friend = insert(:user)
701
702 {:ok, _user_relationship} = User.block(blocker, blockee)
703
704 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
705
706 {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
707
708 assert object = Pleroma.Object.normalize(activity_two, fetch: false)
709
710 data = %{
711 "actor" => friend.ap_id,
712 "object" => object.data["id"],
713 "context" => object.data["context"],
714 "type" => "Announce",
715 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
716 "cc" => [blockee.ap_id]
717 }
718
719 assert {:ok, activity_three} = ActivityPub.insert(data)
720
721 activities =
722 ActivityPub.fetch_activities([], %{blocking_user: blocker})
723 |> Enum.map(fn act -> act.id end)
724
725 assert Enum.member?(activities, activity_one.id)
726 refute Enum.member?(activities, activity_two.id)
727 refute Enum.member?(activities, activity_three.id)
728 end
729
730 test "doesn't return activities from blocked domains" do
731 domain = "dogwhistle.zone"
732 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
733 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
734 activity = insert(:note_activity, %{note: note})
735 user = insert(:user)
736 {:ok, user} = User.block_domain(user, domain)
737
738 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
739
740 refute activity in activities
741
742 followed_user = insert(:user)
743 CommonAPI.follow(user, followed_user)
744 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user)
745
746 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
747
748 refute repeat_activity in activities
749 end
750
751 test "does return activities from followed users on blocked domains" do
752 domain = "meanies.social"
753 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
754 blocker = insert(:user)
755
756 {:ok, blocker, domain_user} = User.follow(blocker, domain_user)
757 {:ok, blocker} = User.block_domain(blocker, domain)
758
759 assert User.following?(blocker, domain_user)
760 assert User.blocks_domain?(blocker, domain_user)
761 refute User.blocks?(blocker, domain_user)
762
763 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
764 activity = insert(:note_activity, %{note: note})
765
766 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
767
768 assert activity in activities
769
770 # And check that if the guy we DO follow boosts someone else from their domain,
771 # that should be hidden
772 another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"})
773 bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}})
774 bad_activity = insert(:note_activity, %{note: bad_note})
775 {:ok, repeat_activity} = CommonAPI.repeat(bad_activity.id, domain_user)
776
777 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
778
779 refute repeat_activity in activities
780 end
781
782 test "returns your own posts regardless of mute" do
783 user = insert(:user)
784 muted = insert(:user)
785
786 {:ok, muted_post} = CommonAPI.post(muted, %{status: "Im stupid"})
787
788 {:ok, reply} =
789 CommonAPI.post(user, %{status: "I'm muting you", in_reply_to_status_id: muted_post.id})
790
791 {:ok, _} = User.mute(user, muted)
792
793 [activity] = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
794
795 assert activity.id == reply.id
796 end
797
798 test "doesn't return muted activities" do
799 activity_one = insert(:note_activity)
800 activity_two = insert(:note_activity)
801 activity_three = insert(:note_activity)
802 user = insert(:user)
803 booster = insert(:user)
804
805 activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
806 {:ok, _user_relationships} = User.mute(user, activity_one_actor)
807
808 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
809
810 assert Enum.member?(activities, activity_two)
811 assert Enum.member?(activities, activity_three)
812 refute Enum.member?(activities, activity_one)
813
814 # Calling with 'with_muted' will deliver muted activities, too.
815 activities =
816 ActivityPub.fetch_activities([], %{
817 muting_user: user,
818 with_muted: true,
819 skip_preload: true
820 })
821
822 assert Enum.member?(activities, activity_two)
823 assert Enum.member?(activities, activity_three)
824 assert Enum.member?(activities, activity_one)
825
826 {:ok, _user_mute} = User.unmute(user, activity_one_actor)
827
828 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
829
830 assert Enum.member?(activities, activity_two)
831 assert Enum.member?(activities, activity_three)
832 assert Enum.member?(activities, activity_one)
833
834 activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
835 {:ok, _user_relationships} = User.mute(user, activity_three_actor)
836 {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
837 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
838 activity_three = Activity.get_by_id(activity_three.id)
839
840 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
841
842 assert Enum.member?(activities, activity_two)
843 refute Enum.member?(activities, activity_three)
844 refute Enum.member?(activities, boost_activity)
845 assert Enum.member?(activities, activity_one)
846
847 activities = ActivityPub.fetch_activities([], %{muting_user: nil, skip_preload: true})
848
849 assert Enum.member?(activities, activity_two)
850 assert Enum.member?(activities, activity_three)
851 assert Enum.member?(activities, boost_activity)
852 assert Enum.member?(activities, activity_one)
853 end
854
855 test "doesn't return thread muted activities" do
856 user = insert(:user)
857 _activity_one = insert(:note_activity)
858 note_two = insert(:note, data: %{"context" => "suya.."})
859 activity_two = insert(:note_activity, note: note_two)
860
861 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
862
863 assert [_activity_one] = ActivityPub.fetch_activities([], %{muting_user: user})
864 end
865
866 test "returns thread muted activities when with_muted is set" do
867 user = insert(:user)
868 _activity_one = insert(:note_activity)
869 note_two = insert(:note, data: %{"context" => "suya.."})
870 activity_two = insert(:note_activity, note: note_two)
871
872 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
873
874 assert [_activity_two, _activity_one] =
875 ActivityPub.fetch_activities([], %{muting_user: user, with_muted: true})
876 end
877
878 test "does include announces on request" do
879 activity_three = insert(:note_activity)
880 user = insert(:user)
881 booster = insert(:user)
882
883 {:ok, user, booster} = User.follow(user, booster)
884
885 {:ok, announce} = CommonAPI.repeat(activity_three.id, booster)
886
887 [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])
888
889 assert announce_activity.id == announce.id
890 end
891
892 test "excludes reblogs on request" do
893 user = insert(:user)
894 {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
895 {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
896
897 [activity] = ActivityPub.fetch_user_activities(user, nil, %{exclude_reblogs: true})
898
899 assert activity == expected_activity
900 end
901
902 describe "irreversible filters" do
903 setup do
904 user = insert(:user)
905 user_two = insert(:user)
906
907 insert(:filter, user: user_two, phrase: "cofe", hide: true)
908 insert(:filter, user: user_two, phrase: "ok boomer", hide: true)
909 insert(:filter, user: user_two, phrase: "test", hide: false)
910
911 params = %{
912 type: ["Create", "Announce"],
913 user: user_two
914 }
915
916 {:ok, %{user: user, user_two: user_two, params: params}}
917 end
918
919 test "it returns statuses if they don't contain exact filter words", %{
920 user: user,
921 params: params
922 } do
923 {:ok, _} = CommonAPI.post(user, %{status: "hey"})
924 {:ok, _} = CommonAPI.post(user, %{status: "got cofefe?"})
925 {:ok, _} = CommonAPI.post(user, %{status: "I am not a boomer"})
926 {:ok, _} = CommonAPI.post(user, %{status: "ok boomers"})
927 {:ok, _} = CommonAPI.post(user, %{status: "ccofee is not a word"})
928 {:ok, _} = CommonAPI.post(user, %{status: "this is a test"})
929
930 activities = ActivityPub.fetch_activities([], params)
931
932 assert Enum.count(activities) == 6
933 end
934
935 test "it does not filter user's own statuses", %{user_two: user_two, params: params} do
936 {:ok, _} = CommonAPI.post(user_two, %{status: "Give me some cofe!"})
937 {:ok, _} = CommonAPI.post(user_two, %{status: "ok boomer"})
938
939 activities = ActivityPub.fetch_activities([], params)
940
941 assert Enum.count(activities) == 2
942 end
943
944 test "it excludes statuses with filter words", %{user: user, params: params} do
945 {:ok, _} = CommonAPI.post(user, %{status: "Give me some cofe!"})
946 {:ok, _} = CommonAPI.post(user, %{status: "ok boomer"})
947 {:ok, _} = CommonAPI.post(user, %{status: "is it a cOfE?"})
948 {:ok, _} = CommonAPI.post(user, %{status: "cofe is all I need"})
949 {:ok, _} = CommonAPI.post(user, %{status: "— ok BOOMER\n"})
950
951 activities = ActivityPub.fetch_activities([], params)
952
953 assert Enum.empty?(activities)
954 end
955
956 test "it returns all statuses if user does not have any filters" do
957 another_user = insert(:user)
958 {:ok, _} = CommonAPI.post(another_user, %{status: "got cofe?"})
959 {:ok, _} = CommonAPI.post(another_user, %{status: "test!"})
960
961 activities =
962 ActivityPub.fetch_activities([], %{
963 type: ["Create", "Announce"],
964 user: another_user
965 })
966
967 assert Enum.count(activities) == 2
968 end
969 end
970
971 describe "public fetch activities" do
972 test "doesn't retrieve unlisted activities" do
973 user = insert(:user)
974
975 {:ok, _unlisted_activity} = CommonAPI.post(user, %{status: "yeah", visibility: "unlisted"})
976
977 {:ok, listed_activity} = CommonAPI.post(user, %{status: "yeah"})
978
979 [activity] = ActivityPub.fetch_public_activities()
980
981 assert activity == listed_activity
982 end
983
984 test "retrieves public activities" do
985 _activities = ActivityPub.fetch_public_activities()
986
987 %{public: public} = ActivityBuilder.public_and_non_public()
988
989 activities = ActivityPub.fetch_public_activities()
990 assert length(activities) == 1
991 assert Enum.at(activities, 0) == public
992 end
993
994 test "retrieves a maximum of 20 activities" do
995 ActivityBuilder.insert_list(10)
996 expected_activities = ActivityBuilder.insert_list(20)
997
998 activities = ActivityPub.fetch_public_activities()
999
1000 assert collect_ids(activities) == collect_ids(expected_activities)
1001 assert length(activities) == 20
1002 end
1003
1004 test "retrieves ids starting from a since_id" do
1005 activities = ActivityBuilder.insert_list(30)
1006 expected_activities = ActivityBuilder.insert_list(10)
1007 since_id = List.last(activities).id
1008
1009 activities = ActivityPub.fetch_public_activities(%{since_id: since_id})
1010
1011 assert collect_ids(activities) == collect_ids(expected_activities)
1012 assert length(activities) == 10
1013 end
1014
1015 test "retrieves ids up to max_id" do
1016 ActivityBuilder.insert_list(10)
1017 expected_activities = ActivityBuilder.insert_list(20)
1018
1019 %{id: max_id} =
1020 10
1021 |> ActivityBuilder.insert_list()
1022 |> List.first()
1023
1024 activities = ActivityPub.fetch_public_activities(%{max_id: max_id})
1025
1026 assert length(activities) == 20
1027 assert collect_ids(activities) == collect_ids(expected_activities)
1028 end
1029
1030 test "paginates via offset/limit" do
1031 _first_part_activities = ActivityBuilder.insert_list(10)
1032 second_part_activities = ActivityBuilder.insert_list(10)
1033
1034 later_activities = ActivityBuilder.insert_list(10)
1035
1036 activities = ActivityPub.fetch_public_activities(%{page: "2", page_size: "20"}, :offset)
1037
1038 assert length(activities) == 20
1039
1040 assert collect_ids(activities) ==
1041 collect_ids(second_part_activities) ++ collect_ids(later_activities)
1042 end
1043
1044 test "doesn't return reblogs for users for whom reblogs have been muted" do
1045 activity = insert(:note_activity)
1046 user = insert(:user)
1047 booster = insert(:user)
1048 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
1049
1050 {:ok, activity} = CommonAPI.repeat(activity.id, booster)
1051
1052 activities = ActivityPub.fetch_activities([], %{muting_user: user})
1053
1054 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
1055 end
1056
1057 test "returns reblogs for users for whom reblogs have not been muted" do
1058 activity = insert(:note_activity)
1059 user = insert(:user)
1060 booster = insert(:user)
1061 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
1062 {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
1063
1064 {:ok, activity} = CommonAPI.repeat(activity.id, booster)
1065
1066 activities = ActivityPub.fetch_activities([], %{muting_user: user})
1067
1068 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
1069 end
1070 end
1071
1072 describe "uploading files" do
1073 setup do
1074 test_file = %Plug.Upload{
1075 content_type: "image/jpeg",
1076 path: Path.absname("test/fixtures/image.jpg"),
1077 filename: "an_image.jpg"
1078 }
1079
1080 %{test_file: test_file}
1081 end
1082
1083 test "sets a description if given", %{test_file: file} do
1084 {:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file")
1085 assert object.data["name"] == "a cool file"
1086 end
1087
1088 test "it sets the default description depending on the configuration", %{test_file: file} do
1089 clear_config([Pleroma.Upload, :default_description])
1090
1091 Pleroma.Config.put([Pleroma.Upload, :default_description], nil)
1092 {:ok, %Object{} = object} = ActivityPub.upload(file)
1093 assert object.data["name"] == ""
1094
1095 Pleroma.Config.put([Pleroma.Upload, :default_description], :filename)
1096 {:ok, %Object{} = object} = ActivityPub.upload(file)
1097 assert object.data["name"] == "an_image.jpg"
1098
1099 Pleroma.Config.put([Pleroma.Upload, :default_description], "unnamed attachment")
1100 {:ok, %Object{} = object} = ActivityPub.upload(file)
1101 assert object.data["name"] == "unnamed attachment"
1102 end
1103
1104 test "copies the file to the configured folder", %{test_file: file} do
1105 clear_config([Pleroma.Upload, :default_description], :filename)
1106 {:ok, %Object{} = object} = ActivityPub.upload(file)
1107 assert object.data["name"] == "an_image.jpg"
1108 end
1109
1110 test "works with base64 encoded images" do
1111 file = %{
1112 img: data_uri()
1113 }
1114
1115 {:ok, %Object{}} = ActivityPub.upload(file)
1116 end
1117 end
1118
1119 describe "fetch the latest Follow" do
1120 test "fetches the latest Follow activity" do
1121 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1122 follower = Repo.get_by(User, ap_id: activity.data["actor"])
1123 followed = Repo.get_by(User, ap_id: activity.data["object"])
1124
1125 assert activity == Utils.fetch_latest_follow(follower, followed)
1126 end
1127 end
1128
1129 describe "unfollowing" do
1130 test "it reverts unfollow activity" do
1131 follower = insert(:user)
1132 followed = insert(:user)
1133
1134 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1135
1136 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1137 assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1138 end
1139
1140 activity = Activity.get_by_id(follow_activity.id)
1141 assert activity.data["type"] == "Follow"
1142 assert activity.data["actor"] == follower.ap_id
1143
1144 assert activity.data["object"] == followed.ap_id
1145 end
1146
1147 test "creates an undo activity for the last follow" do
1148 follower = insert(:user)
1149 followed = insert(:user)
1150
1151 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1152 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1153
1154 assert activity.data["type"] == "Undo"
1155 assert activity.data["actor"] == follower.ap_id
1156
1157 embedded_object = activity.data["object"]
1158 assert is_map(embedded_object)
1159 assert embedded_object["type"] == "Follow"
1160 assert embedded_object["object"] == followed.ap_id
1161 assert embedded_object["id"] == follow_activity.data["id"]
1162 end
1163
1164 test "creates an undo activity for a pending follow request" do
1165 follower = insert(:user)
1166 followed = insert(:user, %{is_locked: true})
1167
1168 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1169 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1170
1171 assert activity.data["type"] == "Undo"
1172 assert activity.data["actor"] == follower.ap_id
1173
1174 embedded_object = activity.data["object"]
1175 assert is_map(embedded_object)
1176 assert embedded_object["type"] == "Follow"
1177 assert embedded_object["object"] == followed.ap_id
1178 assert embedded_object["id"] == follow_activity.data["id"]
1179 end
1180 end
1181
1182 describe "timeline post-processing" do
1183 test "it filters broken threads" do
1184 user1 = insert(:user)
1185 user2 = insert(:user)
1186 user3 = insert(:user)
1187
1188 {:ok, user1, user3} = User.follow(user1, user3)
1189 assert User.following?(user1, user3)
1190
1191 {:ok, user2, user3} = User.follow(user2, user3)
1192 assert User.following?(user2, user3)
1193
1194 {:ok, user3, user2} = User.follow(user3, user2)
1195 assert User.following?(user3, user2)
1196
1197 {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"})
1198
1199 {:ok, private_activity_1} = CommonAPI.post(user3, %{status: "hi 2", visibility: "private"})
1200
1201 {:ok, private_activity_2} =
1202 CommonAPI.post(user2, %{
1203 status: "hi 3",
1204 visibility: "private",
1205 in_reply_to_status_id: private_activity_1.id
1206 })
1207
1208 {:ok, private_activity_3} =
1209 CommonAPI.post(user3, %{
1210 status: "hi 4",
1211 visibility: "private",
1212 in_reply_to_status_id: private_activity_2.id
1213 })
1214
1215 activities =
1216 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1217 |> Enum.map(fn a -> a.id end)
1218
1219 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1220
1221 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1222
1223 assert length(activities) == 3
1224
1225 activities =
1226 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{user: user1})
1227 |> Enum.map(fn a -> a.id end)
1228
1229 assert [public_activity.id, private_activity_1.id] == activities
1230 assert length(activities) == 2
1231 end
1232 end
1233
1234 describe "flag/1" do
1235 setup do
1236 reporter = insert(:user)
1237 target_account = insert(:user)
1238 content = "foobar"
1239 {:ok, activity} = CommonAPI.post(target_account, %{status: content})
1240 context = Utils.generate_context_id()
1241
1242 reporter_ap_id = reporter.ap_id
1243 target_ap_id = target_account.ap_id
1244 activity_ap_id = activity.data["id"]
1245
1246 activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1247
1248 {:ok,
1249 %{
1250 reporter: reporter,
1251 context: context,
1252 target_account: target_account,
1253 reported_activity: activity,
1254 content: content,
1255 activity_ap_id: activity_ap_id,
1256 activity_with_object: activity_with_object,
1257 reporter_ap_id: reporter_ap_id,
1258 target_ap_id: target_ap_id
1259 }}
1260 end
1261
1262 test "it can create a Flag activity",
1263 %{
1264 reporter: reporter,
1265 context: context,
1266 target_account: target_account,
1267 reported_activity: reported_activity,
1268 content: content,
1269 activity_ap_id: activity_ap_id,
1270 activity_with_object: activity_with_object,
1271 reporter_ap_id: reporter_ap_id,
1272 target_ap_id: target_ap_id
1273 } do
1274 assert {:ok, activity} =
1275 ActivityPub.flag(%{
1276 actor: reporter,
1277 context: context,
1278 account: target_account,
1279 statuses: [reported_activity],
1280 content: content
1281 })
1282
1283 note_obj = %{
1284 "type" => "Note",
1285 "id" => activity_ap_id,
1286 "content" => content,
1287 "published" => activity_with_object.object.data["published"],
1288 "actor" =>
1289 AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
1290 }
1291
1292 assert %Activity{
1293 actor: ^reporter_ap_id,
1294 data: %{
1295 "type" => "Flag",
1296 "content" => ^content,
1297 "context" => ^context,
1298 "object" => [^target_ap_id, ^note_obj]
1299 }
1300 } = activity
1301 end
1302
1303 test_with_mock "strips status data from Flag, before federating it",
1304 %{
1305 reporter: reporter,
1306 context: context,
1307 target_account: target_account,
1308 reported_activity: reported_activity,
1309 content: content
1310 },
1311 Utils,
1312 [:passthrough],
1313 [] do
1314 {:ok, activity} =
1315 ActivityPub.flag(%{
1316 actor: reporter,
1317 context: context,
1318 account: target_account,
1319 statuses: [reported_activity],
1320 content: content
1321 })
1322
1323 new_data =
1324 put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]])
1325
1326 assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1327 end
1328
1329 test_with_mock "reverts on error",
1330 %{
1331 reporter: reporter,
1332 context: context,
1333 target_account: target_account,
1334 reported_activity: reported_activity,
1335 content: content
1336 },
1337 Utils,
1338 [:passthrough],
1339 maybe_federate: fn _ -> {:error, :reverted} end do
1340 assert {:error, :reverted} =
1341 ActivityPub.flag(%{
1342 actor: reporter,
1343 context: context,
1344 account: target_account,
1345 statuses: [reported_activity],
1346 content: content
1347 })
1348
1349 assert Repo.aggregate(Activity, :count, :id) == 1
1350 assert Repo.aggregate(Object, :count, :id) == 2
1351 assert Repo.aggregate(Notification, :count, :id) == 0
1352 end
1353 end
1354
1355 test "fetch_activities/2 returns activities addressed to a list " do
1356 user = insert(:user)
1357 member = insert(:user)
1358 {:ok, list} = Pleroma.List.create("foo", user)
1359 {:ok, list} = Pleroma.List.follow(list, member)
1360
1361 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1362
1363 activity = Repo.preload(activity, :bookmark)
1364 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1365
1366 assert ActivityPub.fetch_activities([], %{user: user}) == [activity]
1367 end
1368
1369 def data_uri do
1370 File.read!("test/fixtures/avatar_data_uri")
1371 end
1372
1373 describe "fetch_activities_bounded" do
1374 test "fetches private posts for followed users" do
1375 user = insert(:user)
1376
1377 {:ok, activity} =
1378 CommonAPI.post(user, %{
1379 status: "thought I looked cute might delete later :3",
1380 visibility: "private"
1381 })
1382
1383 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1384 assert result.id == activity.id
1385 end
1386
1387 test "fetches only public posts for other users" do
1388 user = insert(:user)
1389 {:ok, activity} = CommonAPI.post(user, %{status: "#cofe", visibility: "public"})
1390
1391 {:ok, _private_activity} =
1392 CommonAPI.post(user, %{
1393 status: "why is tenshi eating a corndog so cute?",
1394 visibility: "private"
1395 })
1396
1397 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1398 assert result.id == activity.id
1399 end
1400 end
1401
1402 describe "fetch_follow_information_for_user" do
1403 test "syncronizes following/followers counters" do
1404 user =
1405 insert(:user,
1406 local: false,
1407 follower_address: "http://localhost:4001/users/fuser2/followers",
1408 following_address: "http://localhost:4001/users/fuser2/following"
1409 )
1410
1411 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1412 assert info.follower_count == 527
1413 assert info.following_count == 267
1414 end
1415
1416 test "detects hidden followers" do
1417 mock(fn env ->
1418 case env.url do
1419 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1420 %Tesla.Env{status: 403, body: ""}
1421
1422 _ ->
1423 apply(HttpRequestMock, :request, [env])
1424 end
1425 end)
1426
1427 user =
1428 insert(:user,
1429 local: false,
1430 follower_address: "http://localhost:4001/users/masto_closed/followers",
1431 following_address: "http://localhost:4001/users/masto_closed/following"
1432 )
1433
1434 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1435 assert follow_info.hide_followers == true
1436 assert follow_info.hide_follows == false
1437 end
1438
1439 test "detects hidden follows" do
1440 mock(fn env ->
1441 case env.url do
1442 "http://localhost:4001/users/masto_closed/following?page=1" ->
1443 %Tesla.Env{status: 403, body: ""}
1444
1445 _ ->
1446 apply(HttpRequestMock, :request, [env])
1447 end
1448 end)
1449
1450 user =
1451 insert(:user,
1452 local: false,
1453 follower_address: "http://localhost:4001/users/masto_closed/followers",
1454 following_address: "http://localhost:4001/users/masto_closed/following"
1455 )
1456
1457 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1458 assert follow_info.hide_followers == false
1459 assert follow_info.hide_follows == true
1460 end
1461
1462 test "detects hidden follows/followers for friendica" do
1463 user =
1464 insert(:user,
1465 local: false,
1466 follower_address: "http://localhost:8080/followers/fuser3",
1467 following_address: "http://localhost:8080/following/fuser3"
1468 )
1469
1470 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1471 assert follow_info.hide_followers == true
1472 assert follow_info.follower_count == 296
1473 assert follow_info.following_count == 32
1474 assert follow_info.hide_follows == true
1475 end
1476
1477 test "doesn't crash when follower and following counters are hidden" do
1478 mock(fn env ->
1479 case env.url do
1480 "http://localhost:4001/users/masto_hidden_counters/following" ->
1481 json(
1482 %{
1483 "@context" => "https://www.w3.org/ns/activitystreams",
1484 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1485 },
1486 headers: HttpRequestMock.activitypub_object_headers()
1487 )
1488
1489 "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1490 %Tesla.Env{status: 403, body: ""}
1491
1492 "http://localhost:4001/users/masto_hidden_counters/followers" ->
1493 json(
1494 %{
1495 "@context" => "https://www.w3.org/ns/activitystreams",
1496 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1497 },
1498 headers: HttpRequestMock.activitypub_object_headers()
1499 )
1500
1501 "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1502 %Tesla.Env{status: 403, body: ""}
1503 end
1504 end)
1505
1506 user =
1507 insert(:user,
1508 local: false,
1509 follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1510 following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1511 )
1512
1513 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1514
1515 assert follow_info.hide_followers == true
1516 assert follow_info.follower_count == 0
1517 assert follow_info.hide_follows == true
1518 assert follow_info.following_count == 0
1519 end
1520 end
1521
1522 describe "fetch_favourites/3" do
1523 test "returns a favourite activities sorted by adds to favorite" do
1524 user = insert(:user)
1525 other_user = insert(:user)
1526 user1 = insert(:user)
1527 user2 = insert(:user)
1528 {:ok, a1} = CommonAPI.post(user1, %{status: "bla"})
1529 {:ok, _a2} = CommonAPI.post(user2, %{status: "traps are happy"})
1530 {:ok, a3} = CommonAPI.post(user2, %{status: "Trees Are "})
1531 {:ok, a4} = CommonAPI.post(user2, %{status: "Agent Smith "})
1532 {:ok, a5} = CommonAPI.post(user1, %{status: "Red or Blue "})
1533
1534 {:ok, _} = CommonAPI.favorite(user, a4.id)
1535 {:ok, _} = CommonAPI.favorite(other_user, a3.id)
1536 {:ok, _} = CommonAPI.favorite(user, a3.id)
1537 {:ok, _} = CommonAPI.favorite(other_user, a5.id)
1538 {:ok, _} = CommonAPI.favorite(user, a5.id)
1539 {:ok, _} = CommonAPI.favorite(other_user, a4.id)
1540 {:ok, _} = CommonAPI.favorite(user, a1.id)
1541 {:ok, _} = CommonAPI.favorite(other_user, a1.id)
1542 result = ActivityPub.fetch_favourites(user)
1543
1544 assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1545
1546 result = ActivityPub.fetch_favourites(user, %{limit: 2})
1547 assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1548 end
1549 end
1550
1551 describe "Move activity" do
1552 test "create" do
1553 %{ap_id: old_ap_id} = old_user = insert(:user)
1554 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1555 follower = insert(:user)
1556 follower_move_opted_out = insert(:user, allow_following_move: false)
1557
1558 User.follow(follower, old_user)
1559 User.follow(follower_move_opted_out, old_user)
1560
1561 assert User.following?(follower, old_user)
1562 assert User.following?(follower_move_opted_out, old_user)
1563
1564 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1565
1566 assert %Activity{
1567 actor: ^old_ap_id,
1568 data: %{
1569 "actor" => ^old_ap_id,
1570 "object" => ^old_ap_id,
1571 "target" => ^new_ap_id,
1572 "type" => "Move"
1573 },
1574 local: true
1575 } = activity
1576
1577 params = %{
1578 "op" => "move_following",
1579 "origin_id" => old_user.id,
1580 "target_id" => new_user.id
1581 }
1582
1583 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1584
1585 Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
1586
1587 refute User.following?(follower, old_user)
1588 assert User.following?(follower, new_user)
1589
1590 assert User.following?(follower_move_opted_out, old_user)
1591 refute User.following?(follower_move_opted_out, new_user)
1592
1593 activity = %Activity{activity | object: nil}
1594
1595 assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1596
1597 assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1598 end
1599
1600 test "old user must be in the new user's `also_known_as` list" do
1601 old_user = insert(:user)
1602 new_user = insert(:user)
1603
1604 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1605 ActivityPub.move(old_user, new_user)
1606 end
1607 end
1608
1609 test "doesn't retrieve replies activities with exclude_replies" do
1610 user = insert(:user)
1611
1612 {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
1613
1614 {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id})
1615
1616 [result] = ActivityPub.fetch_public_activities(%{exclude_replies: true})
1617
1618 assert result.id == activity.id
1619
1620 assert length(ActivityPub.fetch_public_activities()) == 2
1621 end
1622
1623 describe "replies filtering with public messages" do
1624 setup :public_messages
1625
1626 test "public timeline", %{users: %{u1: user}} do
1627 activities_ids =
1628 %{}
1629 |> Map.put(:type, ["Create", "Announce"])
1630 |> Map.put(:local_only, false)
1631 |> Map.put(:blocking_user, user)
1632 |> Map.put(:muting_user, user)
1633 |> Map.put(:reply_filtering_user, user)
1634 |> ActivityPub.fetch_public_activities()
1635 |> Enum.map(& &1.id)
1636
1637 assert length(activities_ids) == 16
1638 end
1639
1640 test "public timeline with reply_visibility `following`", %{
1641 users: %{u1: user},
1642 u1: u1,
1643 u2: u2,
1644 u3: u3,
1645 u4: u4,
1646 activities: activities
1647 } do
1648 activities_ids =
1649 %{}
1650 |> Map.put(:type, ["Create", "Announce"])
1651 |> Map.put(:local_only, false)
1652 |> Map.put(:blocking_user, user)
1653 |> Map.put(:muting_user, user)
1654 |> Map.put(:reply_visibility, "following")
1655 |> Map.put(:reply_filtering_user, user)
1656 |> ActivityPub.fetch_public_activities()
1657 |> Enum.map(& &1.id)
1658
1659 assert length(activities_ids) == 14
1660
1661 visible_ids =
1662 Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
1663
1664 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1665 end
1666
1667 test "public timeline with reply_visibility `self`", %{
1668 users: %{u1: user},
1669 u1: u1,
1670 u2: u2,
1671 u3: u3,
1672 u4: u4,
1673 activities: activities
1674 } do
1675 activities_ids =
1676 %{}
1677 |> Map.put(:type, ["Create", "Announce"])
1678 |> Map.put(:local_only, false)
1679 |> Map.put(:blocking_user, user)
1680 |> Map.put(:muting_user, user)
1681 |> Map.put(:reply_visibility, "self")
1682 |> Map.put(:reply_filtering_user, user)
1683 |> ActivityPub.fetch_public_activities()
1684 |> Enum.map(& &1.id)
1685
1686 assert length(activities_ids) == 10
1687 visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
1688 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1689 end
1690
1691 test "home timeline", %{
1692 users: %{u1: user},
1693 activities: activities,
1694 u1: u1,
1695 u2: u2,
1696 u3: u3,
1697 u4: u4
1698 } do
1699 params =
1700 %{}
1701 |> Map.put(:type, ["Create", "Announce"])
1702 |> Map.put(:blocking_user, user)
1703 |> Map.put(:muting_user, user)
1704 |> Map.put(:user, user)
1705 |> Map.put(:reply_filtering_user, user)
1706
1707 activities_ids =
1708 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1709 |> Enum.map(& &1.id)
1710
1711 assert length(activities_ids) == 13
1712
1713 visible_ids =
1714 Map.values(u1) ++
1715 Map.values(u3) ++
1716 [
1717 activities[:a1],
1718 activities[:a2],
1719 activities[:a4],
1720 u2[:r1],
1721 u2[:r3],
1722 u4[:r1],
1723 u4[:r2]
1724 ]
1725
1726 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1727 end
1728
1729 test "home timeline with reply_visibility `following`", %{
1730 users: %{u1: user},
1731 activities: activities,
1732 u1: u1,
1733 u2: u2,
1734 u3: u3,
1735 u4: u4
1736 } do
1737 params =
1738 %{}
1739 |> Map.put(:type, ["Create", "Announce"])
1740 |> Map.put(:blocking_user, user)
1741 |> Map.put(:muting_user, user)
1742 |> Map.put(:user, user)
1743 |> Map.put(:reply_visibility, "following")
1744 |> Map.put(:reply_filtering_user, user)
1745
1746 activities_ids =
1747 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1748 |> Enum.map(& &1.id)
1749
1750 assert length(activities_ids) == 11
1751
1752 visible_ids =
1753 Map.values(u1) ++
1754 [
1755 activities[:a1],
1756 activities[:a2],
1757 activities[:a4],
1758 u2[:r1],
1759 u2[:r3],
1760 u3[:r1],
1761 u4[:r1],
1762 u4[:r2]
1763 ]
1764
1765 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1766 end
1767
1768 test "home timeline with reply_visibility `self`", %{
1769 users: %{u1: user},
1770 activities: activities,
1771 u1: u1,
1772 u2: u2,
1773 u3: u3,
1774 u4: u4
1775 } do
1776 params =
1777 %{}
1778 |> Map.put(:type, ["Create", "Announce"])
1779 |> Map.put(:blocking_user, user)
1780 |> Map.put(:muting_user, user)
1781 |> Map.put(:user, user)
1782 |> Map.put(:reply_visibility, "self")
1783 |> Map.put(:reply_filtering_user, user)
1784
1785 activities_ids =
1786 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1787 |> Enum.map(& &1.id)
1788
1789 assert length(activities_ids) == 9
1790
1791 visible_ids =
1792 Map.values(u1) ++
1793 [
1794 activities[:a1],
1795 activities[:a2],
1796 activities[:a4],
1797 u2[:r1],
1798 u3[:r1],
1799 u4[:r1]
1800 ]
1801
1802 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1803 end
1804
1805 test "filtering out announces where the user is the actor of the announced message" do
1806 user = insert(:user)
1807 other_user = insert(:user)
1808 third_user = insert(:user)
1809 User.follow(user, other_user)
1810
1811 {:ok, post} = CommonAPI.post(user, %{status: "yo"})
1812 {:ok, other_post} = CommonAPI.post(third_user, %{status: "yo"})
1813 {:ok, _announce} = CommonAPI.repeat(post.id, other_user)
1814 {:ok, _announce} = CommonAPI.repeat(post.id, third_user)
1815 {:ok, announce} = CommonAPI.repeat(other_post.id, other_user)
1816
1817 params = %{
1818 type: ["Announce"]
1819 }
1820
1821 results =
1822 [user.ap_id | User.following(user)]
1823 |> ActivityPub.fetch_activities(params)
1824
1825 assert length(results) == 3
1826
1827 params = %{
1828 type: ["Announce"],
1829 announce_filtering_user: user
1830 }
1831
1832 [result] =
1833 [user.ap_id | User.following(user)]
1834 |> ActivityPub.fetch_activities(params)
1835
1836 assert result.id == announce.id
1837 end
1838 end
1839
1840 describe "replies filtering with private messages" do
1841 setup :private_messages
1842
1843 test "public timeline", %{users: %{u1: user}} do
1844 activities_ids =
1845 %{}
1846 |> Map.put(:type, ["Create", "Announce"])
1847 |> Map.put(:local_only, false)
1848 |> Map.put(:blocking_user, user)
1849 |> Map.put(:muting_user, user)
1850 |> Map.put(:user, user)
1851 |> ActivityPub.fetch_public_activities()
1852 |> Enum.map(& &1.id)
1853
1854 assert activities_ids == []
1855 end
1856
1857 test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
1858 activities_ids =
1859 %{}
1860 |> Map.put(:type, ["Create", "Announce"])
1861 |> Map.put(:local_only, false)
1862 |> Map.put(:blocking_user, user)
1863 |> Map.put(:muting_user, user)
1864 |> Map.put(:reply_visibility, "following")
1865 |> Map.put(:reply_filtering_user, user)
1866 |> Map.put(:user, user)
1867 |> ActivityPub.fetch_public_activities()
1868 |> Enum.map(& &1.id)
1869
1870 assert activities_ids == []
1871 end
1872
1873 test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
1874 activities_ids =
1875 %{}
1876 |> Map.put(:type, ["Create", "Announce"])
1877 |> Map.put(:local_only, false)
1878 |> Map.put(:blocking_user, user)
1879 |> Map.put(:muting_user, user)
1880 |> Map.put(:reply_visibility, "self")
1881 |> Map.put(:reply_filtering_user, user)
1882 |> Map.put(:user, user)
1883 |> ActivityPub.fetch_public_activities()
1884 |> Enum.map(& &1.id)
1885
1886 assert activities_ids == []
1887
1888 activities_ids =
1889 %{}
1890 |> Map.put(:reply_visibility, "self")
1891 |> Map.put(:reply_filtering_user, nil)
1892 |> ActivityPub.fetch_public_activities()
1893
1894 assert activities_ids == []
1895 end
1896
1897 test "home timeline", %{users: %{u1: user}} do
1898 params =
1899 %{}
1900 |> Map.put(:type, ["Create", "Announce"])
1901 |> Map.put(:blocking_user, user)
1902 |> Map.put(:muting_user, user)
1903 |> Map.put(:user, user)
1904
1905 activities_ids =
1906 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1907 |> Enum.map(& &1.id)
1908
1909 assert length(activities_ids) == 12
1910 end
1911
1912 test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
1913 params =
1914 %{}
1915 |> Map.put(:type, ["Create", "Announce"])
1916 |> Map.put(:blocking_user, user)
1917 |> Map.put(:muting_user, user)
1918 |> Map.put(:user, user)
1919 |> Map.put(:reply_visibility, "following")
1920 |> Map.put(:reply_filtering_user, user)
1921
1922 activities_ids =
1923 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1924 |> Enum.map(& &1.id)
1925
1926 assert length(activities_ids) == 12
1927 end
1928
1929 test "home timeline with default reply_visibility `self`", %{
1930 users: %{u1: user},
1931 activities: activities,
1932 u1: u1,
1933 u2: u2,
1934 u3: u3,
1935 u4: u4
1936 } do
1937 params =
1938 %{}
1939 |> Map.put(:type, ["Create", "Announce"])
1940 |> Map.put(:blocking_user, user)
1941 |> Map.put(:muting_user, user)
1942 |> Map.put(:user, user)
1943 |> Map.put(:reply_visibility, "self")
1944 |> Map.put(:reply_filtering_user, user)
1945
1946 activities_ids =
1947 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1948 |> Enum.map(& &1.id)
1949
1950 assert length(activities_ids) == 10
1951
1952 visible_ids =
1953 Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
1954
1955 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1956 end
1957 end
1958
1959 defp public_messages(_) do
1960 [u1, u2, u3, u4] = insert_list(4, :user)
1961 {:ok, u1, u2} = User.follow(u1, u2)
1962 {:ok, u2, u1} = User.follow(u2, u1)
1963 {:ok, u1, u4} = User.follow(u1, u4)
1964 {:ok, u4, u1} = User.follow(u4, u1)
1965
1966 {:ok, u2, u3} = User.follow(u2, u3)
1967 {:ok, u3, u2} = User.follow(u3, u2)
1968
1969 {:ok, a1} = CommonAPI.post(u1, %{status: "Status"})
1970
1971 {:ok, r1_1} =
1972 CommonAPI.post(u2, %{
1973 status: "@#{u1.nickname} reply from u2 to u1",
1974 in_reply_to_status_id: a1.id
1975 })
1976
1977 {:ok, r1_2} =
1978 CommonAPI.post(u3, %{
1979 status: "@#{u1.nickname} reply from u3 to u1",
1980 in_reply_to_status_id: a1.id
1981 })
1982
1983 {:ok, r1_3} =
1984 CommonAPI.post(u4, %{
1985 status: "@#{u1.nickname} reply from u4 to u1",
1986 in_reply_to_status_id: a1.id
1987 })
1988
1989 {:ok, a2} = CommonAPI.post(u2, %{status: "Status"})
1990
1991 {:ok, r2_1} =
1992 CommonAPI.post(u1, %{
1993 status: "@#{u2.nickname} reply from u1 to u2",
1994 in_reply_to_status_id: a2.id
1995 })
1996
1997 {:ok, r2_2} =
1998 CommonAPI.post(u3, %{
1999 status: "@#{u2.nickname} reply from u3 to u2",
2000 in_reply_to_status_id: a2.id
2001 })
2002
2003 {:ok, r2_3} =
2004 CommonAPI.post(u4, %{
2005 status: "@#{u2.nickname} reply from u4 to u2",
2006 in_reply_to_status_id: a2.id
2007 })
2008
2009 {:ok, a3} = CommonAPI.post(u3, %{status: "Status"})
2010
2011 {:ok, r3_1} =
2012 CommonAPI.post(u1, %{
2013 status: "@#{u3.nickname} reply from u1 to u3",
2014 in_reply_to_status_id: a3.id
2015 })
2016
2017 {:ok, r3_2} =
2018 CommonAPI.post(u2, %{
2019 status: "@#{u3.nickname} reply from u2 to u3",
2020 in_reply_to_status_id: a3.id
2021 })
2022
2023 {:ok, r3_3} =
2024 CommonAPI.post(u4, %{
2025 status: "@#{u3.nickname} reply from u4 to u3",
2026 in_reply_to_status_id: a3.id
2027 })
2028
2029 {:ok, a4} = CommonAPI.post(u4, %{status: "Status"})
2030
2031 {:ok, r4_1} =
2032 CommonAPI.post(u1, %{
2033 status: "@#{u4.nickname} reply from u1 to u4",
2034 in_reply_to_status_id: a4.id
2035 })
2036
2037 {:ok, r4_2} =
2038 CommonAPI.post(u2, %{
2039 status: "@#{u4.nickname} reply from u2 to u4",
2040 in_reply_to_status_id: a4.id
2041 })
2042
2043 {:ok, r4_3} =
2044 CommonAPI.post(u3, %{
2045 status: "@#{u4.nickname} reply from u3 to u4",
2046 in_reply_to_status_id: a4.id
2047 })
2048
2049 {:ok,
2050 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2051 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2052 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2053 u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
2054 u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
2055 u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
2056 end
2057
2058 defp private_messages(_) do
2059 [u1, u2, u3, u4] = insert_list(4, :user)
2060 {:ok, u1, u2} = User.follow(u1, u2)
2061 {:ok, u2, u1} = User.follow(u2, u1)
2062 {:ok, u1, u3} = User.follow(u1, u3)
2063 {:ok, u3, u1} = User.follow(u3, u1)
2064 {:ok, u1, u4} = User.follow(u1, u4)
2065 {:ok, u4, u1} = User.follow(u4, u1)
2066
2067 {:ok, u2, u3} = User.follow(u2, u3)
2068 {:ok, u3, u2} = User.follow(u3, u2)
2069
2070 {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"})
2071
2072 {:ok, r1_1} =
2073 CommonAPI.post(u2, %{
2074 status: "@#{u1.nickname} reply from u2 to u1",
2075 in_reply_to_status_id: a1.id,
2076 visibility: "private"
2077 })
2078
2079 {:ok, r1_2} =
2080 CommonAPI.post(u3, %{
2081 status: "@#{u1.nickname} reply from u3 to u1",
2082 in_reply_to_status_id: a1.id,
2083 visibility: "private"
2084 })
2085
2086 {:ok, r1_3} =
2087 CommonAPI.post(u4, %{
2088 status: "@#{u1.nickname} reply from u4 to u1",
2089 in_reply_to_status_id: a1.id,
2090 visibility: "private"
2091 })
2092
2093 {:ok, a2} = CommonAPI.post(u2, %{status: "Status", visibility: "private"})
2094
2095 {:ok, r2_1} =
2096 CommonAPI.post(u1, %{
2097 status: "@#{u2.nickname} reply from u1 to u2",
2098 in_reply_to_status_id: a2.id,
2099 visibility: "private"
2100 })
2101
2102 {:ok, r2_2} =
2103 CommonAPI.post(u3, %{
2104 status: "@#{u2.nickname} reply from u3 to u2",
2105 in_reply_to_status_id: a2.id,
2106 visibility: "private"
2107 })
2108
2109 {:ok, a3} = CommonAPI.post(u3, %{status: "Status", visibility: "private"})
2110
2111 {:ok, r3_1} =
2112 CommonAPI.post(u1, %{
2113 status: "@#{u3.nickname} reply from u1 to u3",
2114 in_reply_to_status_id: a3.id,
2115 visibility: "private"
2116 })
2117
2118 {:ok, r3_2} =
2119 CommonAPI.post(u2, %{
2120 status: "@#{u3.nickname} reply from u2 to u3",
2121 in_reply_to_status_id: a3.id,
2122 visibility: "private"
2123 })
2124
2125 {:ok, a4} = CommonAPI.post(u4, %{status: "Status", visibility: "private"})
2126
2127 {:ok, r4_1} =
2128 CommonAPI.post(u1, %{
2129 status: "@#{u4.nickname} reply from u1 to u4",
2130 in_reply_to_status_id: a4.id,
2131 visibility: "private"
2132 })
2133
2134 {:ok,
2135 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2136 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2137 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2138 u2: %{r1: r2_1.id, r2: r2_2.id},
2139 u3: %{r1: r3_1.id, r2: r3_2.id},
2140 u4: %{r1: r4_1.id}}
2141 end
2142
2143 describe "maybe_update_follow_information/1" do
2144 setup do
2145 clear_config([:instance, :external_user_synchronization], true)
2146
2147 user = %{
2148 local: false,
2149 ap_id: "https://gensokyo.2hu/users/raymoo",
2150 following_address: "https://gensokyo.2hu/users/following",
2151 follower_address: "https://gensokyo.2hu/users/followers",
2152 type: "Person"
2153 }
2154
2155 %{user: user}
2156 end
2157
2158 test "logs an error when it can't fetch the info", %{user: user} do
2159 assert capture_log(fn ->
2160 ActivityPub.maybe_update_follow_information(user)
2161 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2162 end
2163
2164 test "just returns the input if the user type is Application", %{
2165 user: user
2166 } do
2167 user =
2168 user
2169 |> Map.put(:type, "Application")
2170
2171 refute capture_log(fn ->
2172 assert ^user = ActivityPub.maybe_update_follow_information(user)
2173 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2174 end
2175
2176 test "it just returns the input if the user has no following/follower addresses", %{
2177 user: user
2178 } do
2179 user =
2180 user
2181 |> Map.put(:following_address, nil)
2182 |> Map.put(:follower_address, nil)
2183
2184 refute capture_log(fn ->
2185 assert ^user = ActivityPub.maybe_update_follow_information(user)
2186 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2187 end
2188 end
2189
2190 describe "global activity expiration" do
2191 test "creates an activity expiration for local Create activities" do
2192 clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy)
2193
2194 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
2195 {:ok, follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
2196
2197 assert_enqueued(
2198 worker: Pleroma.Workers.PurgeExpiredActivity,
2199 args: %{activity_id: activity.id},
2200 scheduled_at:
2201 activity.inserted_at
2202 |> DateTime.from_naive!("Etc/UTC")
2203 |> Timex.shift(days: 365)
2204 )
2205
2206 refute_enqueued(
2207 worker: Pleroma.Workers.PurgeExpiredActivity,
2208 args: %{activity_id: follow.id}
2209 )
2210 end
2211 end
2212
2213 describe "handling of clashing nicknames" do
2214 test "renames an existing user with a clashing nickname and a different ap id" do
2215 orig_user =
2216 insert(
2217 :user,
2218 local: false,
2219 nickname: "admin@mastodon.example.org",
2220 ap_id: "http://mastodon.example.org/users/harinezumigari"
2221 )
2222
2223 %{
2224 nickname: orig_user.nickname,
2225 ap_id: orig_user.ap_id <> "part_2"
2226 }
2227 |> ActivityPub.maybe_handle_clashing_nickname()
2228
2229 user = User.get_by_id(orig_user.id)
2230
2231 assert user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
2232 end
2233
2234 test "does nothing with a clashing nickname and the same ap id" do
2235 orig_user =
2236 insert(
2237 :user,
2238 local: false,
2239 nickname: "admin@mastodon.example.org",
2240 ap_id: "http://mastodon.example.org/users/harinezumigari"
2241 )
2242
2243 %{
2244 nickname: orig_user.nickname,
2245 ap_id: orig_user.ap_id
2246 }
2247 |> ActivityPub.maybe_handle_clashing_nickname()
2248
2249 user = User.get_by_id(orig_user.id)
2250
2251 assert user.nickname == orig_user.nickname
2252 end
2253 end
2254
2255 describe "reply filtering" do
2256 test "`following` still contains announcements by friends" do
2257 user = insert(:user)
2258 followed = insert(:user)
2259 not_followed = insert(:user)
2260
2261 User.follow(user, followed)
2262
2263 {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2264
2265 {:ok, not_followed_to_followed} =
2266 CommonAPI.post(not_followed, %{
2267 status: "Also hello",
2268 in_reply_to_status_id: followed_post.id
2269 })
2270
2271 {:ok, retoot} = CommonAPI.repeat(not_followed_to_followed.id, followed)
2272
2273 params =
2274 %{}
2275 |> Map.put(:type, ["Create", "Announce"])
2276 |> Map.put(:blocking_user, user)
2277 |> Map.put(:muting_user, user)
2278 |> Map.put(:reply_filtering_user, user)
2279 |> Map.put(:reply_visibility, "following")
2280 |> Map.put(:announce_filtering_user, user)
2281 |> Map.put(:user, user)
2282
2283 activities =
2284 [user.ap_id | User.following(user)]
2285 |> ActivityPub.fetch_activities(params)
2286
2287 followed_post_id = followed_post.id
2288 retoot_id = retoot.id
2289
2290 assert [%{id: ^followed_post_id}, %{id: ^retoot_id}] = activities
2291
2292 assert length(activities) == 2
2293 end
2294
2295 # This test is skipped because, while this is the desired behavior,
2296 # there seems to be no good way to achieve it with the method that
2297 # we currently use for detecting to who a reply is directed.
2298 # This is a TODO and should be fixed by a later rewrite of the code
2299 # in question.
2300 @tag skip: true
2301 test "`following` still contains self-replies by friends" do
2302 user = insert(:user)
2303 followed = insert(:user)
2304 not_followed = insert(:user)
2305
2306 User.follow(user, followed)
2307
2308 {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2309 {:ok, not_followed_post} = CommonAPI.post(not_followed, %{status: "Also hello"})
2310
2311 {:ok, _followed_to_not_followed} =
2312 CommonAPI.post(followed, %{status: "sup", in_reply_to_status_id: not_followed_post.id})
2313
2314 {:ok, _followed_self_reply} =
2315 CommonAPI.post(followed, %{status: "Also cofe", in_reply_to_status_id: followed_post.id})
2316
2317 params =
2318 %{}
2319 |> Map.put(:type, ["Create", "Announce"])
2320 |> Map.put(:blocking_user, user)
2321 |> Map.put(:muting_user, user)
2322 |> Map.put(:reply_filtering_user, user)
2323 |> Map.put(:reply_visibility, "following")
2324 |> Map.put(:announce_filtering_user, user)
2325 |> Map.put(:user, user)
2326
2327 activities =
2328 [user.ap_id | User.following(user)]
2329 |> ActivityPub.fetch_activities(params)
2330
2331 assert length(activities) == 2
2332 end
2333 end
2334
2335 test "allow fetching of accounts with an empty string name field" do
2336 Tesla.Mock.mock(fn
2337 %{method: :get, url: "https://princess.cat/users/mewmew"} ->
2338 file = File.read!("test/fixtures/mewmew_no_name.json")
2339 %Tesla.Env{status: 200, body: file, headers: HttpRequestMock.activitypub_object_headers()}
2340 end)
2341
2342 {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew")
2343 assert user.name == " "
2344 end
2345 end