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