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