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