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