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