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