implement Move activities (#45)
[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 recipients: recipients
1782 } = activity
1783
1784 assert old_user.follower_address in recipients
1785
1786 params = %{
1787 "op" => "move_following",
1788 "origin_id" => old_user.id,
1789 "target_id" => new_user.id
1790 }
1791
1792 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1793
1794 Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
1795
1796 refute User.following?(follower, old_user)
1797 assert User.following?(follower, new_user)
1798
1799 assert User.following?(follower_move_opted_out, old_user)
1800 refute User.following?(follower_move_opted_out, new_user)
1801
1802 activity = %Activity{activity | object: nil}
1803
1804 assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1805
1806 assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1807 end
1808
1809 test "old user must be in the new user's `also_known_as` list" do
1810 old_user = insert(:user)
1811 new_user = insert(:user)
1812
1813 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1814 ActivityPub.move(old_user, new_user)
1815 end
1816
1817 test "do not move remote user following relationships" do
1818 %{ap_id: old_ap_id} = old_user = insert(:user)
1819 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1820 follower_remote = insert(:user, local: false)
1821
1822 User.follow(follower_remote, old_user)
1823
1824 assert User.following?(follower_remote, old_user)
1825
1826 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1827
1828 assert %Activity{
1829 actor: ^old_ap_id,
1830 data: %{
1831 "actor" => ^old_ap_id,
1832 "object" => ^old_ap_id,
1833 "target" => ^new_ap_id,
1834 "type" => "Move"
1835 },
1836 local: true
1837 } = activity
1838
1839 params = %{
1840 "op" => "move_following",
1841 "origin_id" => old_user.id,
1842 "target_id" => new_user.id
1843 }
1844
1845 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1846
1847 Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
1848
1849 assert User.following?(follower_remote, old_user)
1850 refute User.following?(follower_remote, new_user)
1851 end
1852 end
1853
1854 test "doesn't retrieve replies activities with exclude_replies" do
1855 user = insert(:user)
1856
1857 {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
1858
1859 {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id})
1860
1861 [result] = ActivityPub.fetch_public_activities(%{exclude_replies: true})
1862
1863 assert result.id == activity.id
1864
1865 assert length(ActivityPub.fetch_public_activities()) == 2
1866 end
1867
1868 describe "replies filtering with public messages" do
1869 setup :public_messages
1870
1871 test "public timeline", %{users: %{u1: user}} do
1872 activities_ids =
1873 %{}
1874 |> Map.put(:type, ["Create", "Announce"])
1875 |> Map.put(:local_only, false)
1876 |> Map.put(:blocking_user, user)
1877 |> Map.put(:muting_user, user)
1878 |> Map.put(:reply_filtering_user, user)
1879 |> ActivityPub.fetch_public_activities()
1880 |> Enum.map(& &1.id)
1881
1882 assert length(activities_ids) == 16
1883 end
1884
1885 test "public timeline with reply_visibility `following`", %{
1886 users: %{u1: user},
1887 u1: u1,
1888 u2: u2,
1889 u3: u3,
1890 u4: u4,
1891 activities: activities
1892 } do
1893 activities_ids =
1894 %{}
1895 |> Map.put(:type, ["Create", "Announce"])
1896 |> Map.put(:local_only, false)
1897 |> Map.put(:blocking_user, user)
1898 |> Map.put(:muting_user, user)
1899 |> Map.put(:reply_visibility, "following")
1900 |> Map.put(:reply_filtering_user, user)
1901 |> ActivityPub.fetch_public_activities()
1902 |> Enum.map(& &1.id)
1903
1904 assert length(activities_ids) == 14
1905
1906 visible_ids =
1907 Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
1908
1909 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1910 end
1911
1912 test "public timeline with reply_visibility `self`", %{
1913 users: %{u1: user},
1914 u1: u1,
1915 u2: u2,
1916 u3: u3,
1917 u4: u4,
1918 activities: activities
1919 } do
1920 activities_ids =
1921 %{}
1922 |> Map.put(:type, ["Create", "Announce"])
1923 |> Map.put(:local_only, false)
1924 |> Map.put(:blocking_user, user)
1925 |> Map.put(:muting_user, user)
1926 |> Map.put(:reply_visibility, "self")
1927 |> Map.put(:reply_filtering_user, user)
1928 |> ActivityPub.fetch_public_activities()
1929 |> Enum.map(& &1.id)
1930
1931 assert length(activities_ids) == 10
1932 visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
1933 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1934 end
1935
1936 test "home timeline", %{
1937 users: %{u1: user},
1938 activities: activities,
1939 u1: u1,
1940 u2: u2,
1941 u3: u3,
1942 u4: u4
1943 } do
1944 params =
1945 %{}
1946 |> Map.put(:type, ["Create", "Announce"])
1947 |> Map.put(:blocking_user, user)
1948 |> Map.put(:muting_user, user)
1949 |> Map.put(:user, user)
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) == 13
1957
1958 visible_ids =
1959 Map.values(u1) ++
1960 Map.values(u3) ++
1961 [
1962 activities[:a1],
1963 activities[:a2],
1964 activities[:a4],
1965 u2[:r1],
1966 u2[:r3],
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 `following`", %{
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, "following")
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) == 11
1996
1997 visible_ids =
1998 Map.values(u1) ++
1999 [
2000 activities[:a1],
2001 activities[:a2],
2002 activities[:a4],
2003 u2[:r1],
2004 u2[:r3],
2005 u3[:r1],
2006 u4[:r1],
2007 u4[:r2]
2008 ]
2009
2010 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2011 end
2012
2013 test "home timeline with reply_visibility `self`", %{
2014 users: %{u1: user},
2015 activities: activities,
2016 u1: u1,
2017 u2: u2,
2018 u3: u3,
2019 u4: u4
2020 } do
2021 params =
2022 %{}
2023 |> Map.put(:type, ["Create", "Announce"])
2024 |> Map.put(:blocking_user, user)
2025 |> Map.put(:muting_user, user)
2026 |> Map.put(:user, user)
2027 |> Map.put(:reply_visibility, "self")
2028 |> Map.put(:reply_filtering_user, user)
2029
2030 activities_ids =
2031 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2032 |> Enum.map(& &1.id)
2033
2034 assert length(activities_ids) == 9
2035
2036 visible_ids =
2037 Map.values(u1) ++
2038 [
2039 activities[:a1],
2040 activities[:a2],
2041 activities[:a4],
2042 u2[:r1],
2043 u3[:r1],
2044 u4[:r1]
2045 ]
2046
2047 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2048 end
2049
2050 test "filtering out announces where the user is the actor of the announced message" do
2051 user = insert(:user)
2052 other_user = insert(:user)
2053 third_user = insert(:user)
2054 User.follow(user, other_user)
2055
2056 {:ok, post} = CommonAPI.post(user, %{status: "yo"})
2057 {:ok, other_post} = CommonAPI.post(third_user, %{status: "yo"})
2058 {:ok, _announce} = CommonAPI.repeat(post.id, other_user)
2059 {:ok, _announce} = CommonAPI.repeat(post.id, third_user)
2060 {:ok, announce} = CommonAPI.repeat(other_post.id, other_user)
2061
2062 params = %{
2063 type: ["Announce"]
2064 }
2065
2066 results =
2067 [user.ap_id | User.following(user)]
2068 |> ActivityPub.fetch_activities(params)
2069
2070 assert length(results) == 3
2071
2072 params = %{
2073 type: ["Announce"],
2074 announce_filtering_user: user
2075 }
2076
2077 [result] =
2078 [user.ap_id | User.following(user)]
2079 |> ActivityPub.fetch_activities(params)
2080
2081 assert result.id == announce.id
2082 end
2083 end
2084
2085 describe "replies filtering with private messages" do
2086 setup :private_messages
2087
2088 test "public timeline", %{users: %{u1: user}} do
2089 activities_ids =
2090 %{}
2091 |> Map.put(:type, ["Create", "Announce"])
2092 |> Map.put(:local_only, false)
2093 |> Map.put(:blocking_user, user)
2094 |> Map.put(:muting_user, user)
2095 |> Map.put(:user, user)
2096 |> ActivityPub.fetch_public_activities()
2097 |> Enum.map(& &1.id)
2098
2099 assert activities_ids == []
2100 end
2101
2102 test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2103 activities_ids =
2104 %{}
2105 |> Map.put(:type, ["Create", "Announce"])
2106 |> Map.put(:local_only, false)
2107 |> Map.put(:blocking_user, user)
2108 |> Map.put(:muting_user, user)
2109 |> Map.put(:reply_visibility, "following")
2110 |> Map.put(:reply_filtering_user, user)
2111 |> Map.put(:user, user)
2112 |> ActivityPub.fetch_public_activities()
2113 |> Enum.map(& &1.id)
2114
2115 assert activities_ids == []
2116 end
2117
2118 test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
2119 activities_ids =
2120 %{}
2121 |> Map.put(:type, ["Create", "Announce"])
2122 |> Map.put(:local_only, false)
2123 |> Map.put(:blocking_user, user)
2124 |> Map.put(:muting_user, user)
2125 |> Map.put(:reply_visibility, "self")
2126 |> Map.put(:reply_filtering_user, user)
2127 |> Map.put(:user, user)
2128 |> ActivityPub.fetch_public_activities()
2129 |> Enum.map(& &1.id)
2130
2131 assert activities_ids == []
2132
2133 activities_ids =
2134 %{}
2135 |> Map.put(:reply_visibility, "self")
2136 |> Map.put(:reply_filtering_user, nil)
2137 |> ActivityPub.fetch_public_activities()
2138
2139 assert activities_ids == []
2140 end
2141
2142 test "home timeline", %{users: %{u1: user}} 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
2150 activities_ids =
2151 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2152 |> Enum.map(& &1.id)
2153
2154 assert length(activities_ids) == 12
2155 end
2156
2157 test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2158 params =
2159 %{}
2160 |> Map.put(:type, ["Create", "Announce"])
2161 |> Map.put(:blocking_user, user)
2162 |> Map.put(:muting_user, user)
2163 |> Map.put(:user, user)
2164 |> Map.put(:reply_visibility, "following")
2165 |> Map.put(:reply_filtering_user, user)
2166
2167 activities_ids =
2168 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2169 |> Enum.map(& &1.id)
2170
2171 assert length(activities_ids) == 12
2172 end
2173
2174 test "home timeline with default reply_visibility `self`", %{
2175 users: %{u1: user},
2176 activities: activities,
2177 u1: u1,
2178 u2: u2,
2179 u3: u3,
2180 u4: u4
2181 } do
2182 params =
2183 %{}
2184 |> Map.put(:type, ["Create", "Announce"])
2185 |> Map.put(:blocking_user, user)
2186 |> Map.put(:muting_user, user)
2187 |> Map.put(:user, user)
2188 |> Map.put(:reply_visibility, "self")
2189 |> Map.put(:reply_filtering_user, user)
2190
2191 activities_ids =
2192 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2193 |> Enum.map(& &1.id)
2194
2195 assert length(activities_ids) == 10
2196
2197 visible_ids =
2198 Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
2199
2200 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2201 end
2202 end
2203
2204 defp public_messages(_) do
2205 [u1, u2, u3, u4] = insert_list(4, :user)
2206 {:ok, u1, u2} = User.follow(u1, u2)
2207 {:ok, u2, u1} = User.follow(u2, u1)
2208 {:ok, u1, u4} = User.follow(u1, u4)
2209 {:ok, u4, u1} = User.follow(u4, u1)
2210
2211 {:ok, u2, u3} = User.follow(u2, u3)
2212 {:ok, u3, u2} = User.follow(u3, u2)
2213
2214 {:ok, a1} = CommonAPI.post(u1, %{status: "Status"})
2215
2216 {:ok, r1_1} =
2217 CommonAPI.post(u2, %{
2218 status: "@#{u1.nickname} reply from u2 to u1",
2219 in_reply_to_status_id: a1.id
2220 })
2221
2222 {:ok, r1_2} =
2223 CommonAPI.post(u3, %{
2224 status: "@#{u1.nickname} reply from u3 to u1",
2225 in_reply_to_status_id: a1.id
2226 })
2227
2228 {:ok, r1_3} =
2229 CommonAPI.post(u4, %{
2230 status: "@#{u1.nickname} reply from u4 to u1",
2231 in_reply_to_status_id: a1.id
2232 })
2233
2234 {:ok, a2} = CommonAPI.post(u2, %{status: "Status"})
2235
2236 {:ok, r2_1} =
2237 CommonAPI.post(u1, %{
2238 status: "@#{u2.nickname} reply from u1 to u2",
2239 in_reply_to_status_id: a2.id
2240 })
2241
2242 {:ok, r2_2} =
2243 CommonAPI.post(u3, %{
2244 status: "@#{u2.nickname} reply from u3 to u2",
2245 in_reply_to_status_id: a2.id
2246 })
2247
2248 {:ok, r2_3} =
2249 CommonAPI.post(u4, %{
2250 status: "@#{u2.nickname} reply from u4 to u2",
2251 in_reply_to_status_id: a2.id
2252 })
2253
2254 {:ok, a3} = CommonAPI.post(u3, %{status: "Status"})
2255
2256 {:ok, r3_1} =
2257 CommonAPI.post(u1, %{
2258 status: "@#{u3.nickname} reply from u1 to u3",
2259 in_reply_to_status_id: a3.id
2260 })
2261
2262 {:ok, r3_2} =
2263 CommonAPI.post(u2, %{
2264 status: "@#{u3.nickname} reply from u2 to u3",
2265 in_reply_to_status_id: a3.id
2266 })
2267
2268 {:ok, r3_3} =
2269 CommonAPI.post(u4, %{
2270 status: "@#{u3.nickname} reply from u4 to u3",
2271 in_reply_to_status_id: a3.id
2272 })
2273
2274 {:ok, a4} = CommonAPI.post(u4, %{status: "Status"})
2275
2276 {:ok, r4_1} =
2277 CommonAPI.post(u1, %{
2278 status: "@#{u4.nickname} reply from u1 to u4",
2279 in_reply_to_status_id: a4.id
2280 })
2281
2282 {:ok, r4_2} =
2283 CommonAPI.post(u2, %{
2284 status: "@#{u4.nickname} reply from u2 to u4",
2285 in_reply_to_status_id: a4.id
2286 })
2287
2288 {:ok, r4_3} =
2289 CommonAPI.post(u3, %{
2290 status: "@#{u4.nickname} reply from u3 to u4",
2291 in_reply_to_status_id: a4.id
2292 })
2293
2294 {:ok,
2295 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2296 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2297 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2298 u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
2299 u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
2300 u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
2301 end
2302
2303 defp private_messages(_) do
2304 [u1, u2, u3, u4] = insert_list(4, :user)
2305 {:ok, u1, u2} = User.follow(u1, u2)
2306 {:ok, u2, u1} = User.follow(u2, u1)
2307 {:ok, u1, u3} = User.follow(u1, u3)
2308 {:ok, u3, u1} = User.follow(u3, u1)
2309 {:ok, u1, u4} = User.follow(u1, u4)
2310 {:ok, u4, u1} = User.follow(u4, u1)
2311
2312 {:ok, u2, u3} = User.follow(u2, u3)
2313 {:ok, u3, u2} = User.follow(u3, u2)
2314
2315 {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"})
2316
2317 {:ok, r1_1} =
2318 CommonAPI.post(u2, %{
2319 status: "@#{u1.nickname} reply from u2 to u1",
2320 in_reply_to_status_id: a1.id,
2321 visibility: "private"
2322 })
2323
2324 {:ok, r1_2} =
2325 CommonAPI.post(u3, %{
2326 status: "@#{u1.nickname} reply from u3 to u1",
2327 in_reply_to_status_id: a1.id,
2328 visibility: "private"
2329 })
2330
2331 {:ok, r1_3} =
2332 CommonAPI.post(u4, %{
2333 status: "@#{u1.nickname} reply from u4 to u1",
2334 in_reply_to_status_id: a1.id,
2335 visibility: "private"
2336 })
2337
2338 {:ok, a2} = CommonAPI.post(u2, %{status: "Status", visibility: "private"})
2339
2340 {:ok, r2_1} =
2341 CommonAPI.post(u1, %{
2342 status: "@#{u2.nickname} reply from u1 to u2",
2343 in_reply_to_status_id: a2.id,
2344 visibility: "private"
2345 })
2346
2347 {:ok, r2_2} =
2348 CommonAPI.post(u3, %{
2349 status: "@#{u2.nickname} reply from u3 to u2",
2350 in_reply_to_status_id: a2.id,
2351 visibility: "private"
2352 })
2353
2354 {:ok, a3} = CommonAPI.post(u3, %{status: "Status", visibility: "private"})
2355
2356 {:ok, r3_1} =
2357 CommonAPI.post(u1, %{
2358 status: "@#{u3.nickname} reply from u1 to u3",
2359 in_reply_to_status_id: a3.id,
2360 visibility: "private"
2361 })
2362
2363 {:ok, r3_2} =
2364 CommonAPI.post(u2, %{
2365 status: "@#{u3.nickname} reply from u2 to u3",
2366 in_reply_to_status_id: a3.id,
2367 visibility: "private"
2368 })
2369
2370 {:ok, a4} = CommonAPI.post(u4, %{status: "Status", visibility: "private"})
2371
2372 {:ok, r4_1} =
2373 CommonAPI.post(u1, %{
2374 status: "@#{u4.nickname} reply from u1 to u4",
2375 in_reply_to_status_id: a4.id,
2376 visibility: "private"
2377 })
2378
2379 {:ok,
2380 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2381 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2382 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2383 u2: %{r1: r2_1.id, r2: r2_2.id},
2384 u3: %{r1: r3_1.id, r2: r3_2.id},
2385 u4: %{r1: r4_1.id}}
2386 end
2387
2388 describe "maybe_update_follow_information/1" do
2389 setup do
2390 clear_config([:instance, :external_user_synchronization], true)
2391
2392 user = %{
2393 local: false,
2394 ap_id: "https://gensokyo.2hu/users/raymoo",
2395 following_address: "https://gensokyo.2hu/users/following",
2396 follower_address: "https://gensokyo.2hu/users/followers",
2397 type: "Person"
2398 }
2399
2400 %{user: user}
2401 end
2402
2403 test "logs an error when it can't fetch the info", %{user: user} do
2404 assert capture_log(fn ->
2405 ActivityPub.maybe_update_follow_information(user)
2406 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2407 end
2408
2409 test "just returns the input if the user type is Application", %{
2410 user: user
2411 } do
2412 user =
2413 user
2414 |> Map.put(:type, "Application")
2415
2416 refute capture_log(fn ->
2417 assert ^user = ActivityPub.maybe_update_follow_information(user)
2418 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2419 end
2420
2421 test "it just returns the input if the user has no following/follower addresses", %{
2422 user: user
2423 } do
2424 user =
2425 user
2426 |> Map.put(:following_address, nil)
2427 |> Map.put(:follower_address, nil)
2428
2429 refute capture_log(fn ->
2430 assert ^user = ActivityPub.maybe_update_follow_information(user)
2431 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2432 end
2433 end
2434
2435 describe "global activity expiration" do
2436 test "creates an activity expiration for local Create activities" do
2437 clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy)
2438
2439 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
2440 {:ok, follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
2441
2442 assert_enqueued(
2443 worker: Pleroma.Workers.PurgeExpiredActivity,
2444 args: %{activity_id: activity.id},
2445 scheduled_at:
2446 activity.inserted_at
2447 |> DateTime.from_naive!("Etc/UTC")
2448 |> Timex.shift(days: 365)
2449 )
2450
2451 refute_enqueued(
2452 worker: Pleroma.Workers.PurgeExpiredActivity,
2453 args: %{activity_id: follow.id}
2454 )
2455 end
2456 end
2457
2458 describe "handling of clashing nicknames" do
2459 test "renames an existing user with a clashing nickname and a different ap id" do
2460 orig_user =
2461 insert(
2462 :user,
2463 local: false,
2464 nickname: "admin@mastodon.example.org",
2465 ap_id: "http://mastodon.example.org/users/harinezumigari"
2466 )
2467
2468 %{
2469 nickname: orig_user.nickname,
2470 ap_id: orig_user.ap_id <> "part_2"
2471 }
2472 |> ActivityPub.maybe_handle_clashing_nickname()
2473
2474 user = User.get_by_id(orig_user.id)
2475
2476 assert user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
2477 end
2478
2479 test "does nothing with a clashing nickname and the same ap id" do
2480 orig_user =
2481 insert(
2482 :user,
2483 local: false,
2484 nickname: "admin@mastodon.example.org",
2485 ap_id: "http://mastodon.example.org/users/harinezumigari"
2486 )
2487
2488 %{
2489 nickname: orig_user.nickname,
2490 ap_id: orig_user.ap_id
2491 }
2492 |> ActivityPub.maybe_handle_clashing_nickname()
2493
2494 user = User.get_by_id(orig_user.id)
2495
2496 assert user.nickname == orig_user.nickname
2497 end
2498 end
2499
2500 describe "reply filtering" do
2501 test "`following` still contains announcements by friends" do
2502 user = insert(:user)
2503 followed = insert(:user)
2504 not_followed = insert(:user)
2505
2506 User.follow(user, followed)
2507
2508 {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2509
2510 {:ok, not_followed_to_followed} =
2511 CommonAPI.post(not_followed, %{
2512 status: "Also hello",
2513 in_reply_to_status_id: followed_post.id
2514 })
2515
2516 {:ok, retoot} = CommonAPI.repeat(not_followed_to_followed.id, followed)
2517
2518 params =
2519 %{}
2520 |> Map.put(:type, ["Create", "Announce"])
2521 |> Map.put(:blocking_user, user)
2522 |> Map.put(:muting_user, user)
2523 |> Map.put(:reply_filtering_user, user)
2524 |> Map.put(:reply_visibility, "following")
2525 |> Map.put(:announce_filtering_user, user)
2526 |> Map.put(:user, user)
2527
2528 activities =
2529 [user.ap_id | User.following(user)]
2530 |> ActivityPub.fetch_activities(params)
2531
2532 followed_post_id = followed_post.id
2533 retoot_id = retoot.id
2534
2535 assert [%{id: ^followed_post_id}, %{id: ^retoot_id}] = activities
2536
2537 assert length(activities) == 2
2538 end
2539
2540 # This test is skipped because, while this is the desired behavior,
2541 # there seems to be no good way to achieve it with the method that
2542 # we currently use for detecting to who a reply is directed.
2543 # This is a TODO and should be fixed by a later rewrite of the code
2544 # in question.
2545 @tag skip: true
2546 test "`following` still contains self-replies by friends" do
2547 user = insert(:user)
2548 followed = insert(:user)
2549 not_followed = insert(:user)
2550
2551 User.follow(user, followed)
2552
2553 {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2554 {:ok, not_followed_post} = CommonAPI.post(not_followed, %{status: "Also hello"})
2555
2556 {:ok, _followed_to_not_followed} =
2557 CommonAPI.post(followed, %{status: "sup", in_reply_to_status_id: not_followed_post.id})
2558
2559 {:ok, _followed_self_reply} =
2560 CommonAPI.post(followed, %{status: "Also cofe", in_reply_to_status_id: followed_post.id})
2561
2562 params =
2563 %{}
2564 |> Map.put(:type, ["Create", "Announce"])
2565 |> Map.put(:blocking_user, user)
2566 |> Map.put(:muting_user, user)
2567 |> Map.put(:reply_filtering_user, user)
2568 |> Map.put(:reply_visibility, "following")
2569 |> Map.put(:announce_filtering_user, user)
2570 |> Map.put(:user, user)
2571
2572 activities =
2573 [user.ap_id | User.following(user)]
2574 |> ActivityPub.fetch_activities(params)
2575
2576 assert length(activities) == 2
2577 end
2578 end
2579
2580 test "allow fetching of accounts with an empty string name field" do
2581 Tesla.Mock.mock(fn
2582 %{method: :get, url: "https://princess.cat/users/mewmew"} ->
2583 file = File.read!("test/fixtures/mewmew_no_name.json")
2584 %Tesla.Env{status: 200, body: file, headers: HttpRequestMock.activitypub_object_headers()}
2585 end)
2586
2587 {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew")
2588 assert user.name == " "
2589 end
2590 end