17c52fc912354a460665445a8860533c05cf8acc
[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 "sets a description if given", %{test_file: file} do
1307 {:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file")
1308 assert object.data["name"] == "a cool file"
1309 end
1310
1311 test "it sets the default description depending on the configuration", %{test_file: file} do
1312 clear_config([Pleroma.Upload, :default_description])
1313
1314 clear_config([Pleroma.Upload, :default_description], nil)
1315 {:ok, %Object{} = object} = ActivityPub.upload(file)
1316 assert object.data["name"] == ""
1317
1318 clear_config([Pleroma.Upload, :default_description], :filename)
1319 {:ok, %Object{} = object} = ActivityPub.upload(file)
1320 assert object.data["name"] == "an_image.jpg"
1321
1322 clear_config([Pleroma.Upload, :default_description], "unnamed attachment")
1323 {:ok, %Object{} = object} = ActivityPub.upload(file)
1324 assert object.data["name"] == "unnamed attachment"
1325 end
1326
1327 test "copies the file to the configured folder", %{test_file: file} do
1328 clear_config([Pleroma.Upload, :default_description], :filename)
1329 {:ok, %Object{} = object} = ActivityPub.upload(file)
1330 assert object.data["name"] == "an_image.jpg"
1331 end
1332
1333 test "works with base64 encoded images" do
1334 file = %{
1335 img: data_uri()
1336 }
1337
1338 {:ok, %Object{}} = ActivityPub.upload(file)
1339 end
1340 end
1341
1342 describe "fetch the latest Follow" do
1343 test "fetches the latest Follow activity" do
1344 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1345 follower = Repo.get_by(User, ap_id: activity.data["actor"])
1346 followed = Repo.get_by(User, ap_id: activity.data["object"])
1347
1348 assert activity == Utils.fetch_latest_follow(follower, followed)
1349 end
1350 end
1351
1352 describe "unfollowing" do
1353 test "it reverts unfollow activity" do
1354 follower = insert(:user)
1355 followed = insert(:user)
1356
1357 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1358
1359 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1360 assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1361 end
1362
1363 activity = Activity.get_by_id(follow_activity.id)
1364 assert activity.data["type"] == "Follow"
1365 assert activity.data["actor"] == follower.ap_id
1366
1367 assert activity.data["object"] == followed.ap_id
1368 end
1369
1370 test "creates an undo activity for the last follow" do
1371 follower = insert(:user)
1372 followed = insert(:user)
1373
1374 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1375 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1376
1377 assert activity.data["type"] == "Undo"
1378 assert activity.data["actor"] == follower.ap_id
1379
1380 embedded_object = activity.data["object"]
1381 assert is_map(embedded_object)
1382 assert embedded_object["type"] == "Follow"
1383 assert embedded_object["object"] == followed.ap_id
1384 assert embedded_object["id"] == follow_activity.data["id"]
1385 end
1386
1387 test "creates an undo activity for a pending follow request" do
1388 follower = insert(:user)
1389 followed = insert(:user, %{is_locked: true})
1390
1391 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1392 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1393
1394 assert activity.data["type"] == "Undo"
1395 assert activity.data["actor"] == follower.ap_id
1396
1397 embedded_object = activity.data["object"]
1398 assert is_map(embedded_object)
1399 assert embedded_object["type"] == "Follow"
1400 assert embedded_object["object"] == followed.ap_id
1401 assert embedded_object["id"] == follow_activity.data["id"]
1402 end
1403
1404 test "it removes the follow activity if it was local" do
1405 follower = insert(:user, local: true)
1406 followed = insert(:user)
1407
1408 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1409 {:ok, activity} = ActivityPub.unfollow(follower, followed, nil, true)
1410
1411 assert activity.data["type"] == "Undo"
1412 assert activity.data["actor"] == follower.ap_id
1413
1414 follow_activity = Activity.get_by_id(follow_activity.id)
1415 assert is_nil(follow_activity)
1416 assert is_nil(Utils.fetch_latest_follow(follower, followed))
1417
1418 # We need to keep our own undo
1419 undo_activity = Activity.get_by_ap_id(activity.data["id"])
1420 refute is_nil(undo_activity)
1421 end
1422
1423 test "it removes the follow activity if it was remote" do
1424 follower = insert(:user, local: false)
1425 followed = insert(:user)
1426
1427 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1428 {:ok, activity} = ActivityPub.unfollow(follower, followed, nil, false)
1429
1430 assert activity.data["type"] == "Undo"
1431 assert activity.data["actor"] == follower.ap_id
1432
1433 follow_activity = Activity.get_by_id(follow_activity.id)
1434 assert is_nil(follow_activity)
1435 assert is_nil(Utils.fetch_latest_follow(follower, followed))
1436
1437 undo_activity = Activity.get_by_ap_id(activity.data["id"])
1438 assert is_nil(undo_activity)
1439 end
1440 end
1441
1442 describe "timeline post-processing" do
1443 test "it filters broken threads" do
1444 user1 = insert(:user)
1445 user2 = insert(:user)
1446 user3 = insert(:user)
1447
1448 {:ok, user1, user3} = User.follow(user1, user3)
1449 assert User.following?(user1, user3)
1450
1451 {:ok, user2, user3} = User.follow(user2, user3)
1452 assert User.following?(user2, user3)
1453
1454 {:ok, user3, user2} = User.follow(user3, user2)
1455 assert User.following?(user3, user2)
1456
1457 {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"})
1458
1459 {:ok, private_activity_1} = CommonAPI.post(user3, %{status: "hi 2", visibility: "private"})
1460
1461 {:ok, private_activity_2} =
1462 CommonAPI.post(user2, %{
1463 status: "hi 3",
1464 visibility: "private",
1465 in_reply_to_status_id: private_activity_1.id
1466 })
1467
1468 {:ok, private_activity_3} =
1469 CommonAPI.post(user3, %{
1470 status: "hi 4",
1471 visibility: "private",
1472 in_reply_to_status_id: private_activity_2.id
1473 })
1474
1475 activities =
1476 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1477 |> Enum.map(fn a -> a.id end)
1478
1479 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1480
1481 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1482
1483 assert length(activities) == 3
1484
1485 activities =
1486 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{user: user1})
1487 |> Enum.map(fn a -> a.id end)
1488
1489 assert [public_activity.id, private_activity_1.id] == activities
1490 assert length(activities) == 2
1491 end
1492 end
1493
1494 describe "flag/1" do
1495 setup do
1496 reporter = insert(:user)
1497 target_account = insert(:user)
1498 content = "foobar"
1499 {:ok, activity} = CommonAPI.post(target_account, %{status: content})
1500 context = Utils.generate_context_id()
1501
1502 reporter_ap_id = reporter.ap_id
1503 target_ap_id = target_account.ap_id
1504 activity_ap_id = activity.data["id"]
1505
1506 activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1507
1508 {:ok,
1509 %{
1510 reporter: reporter,
1511 context: context,
1512 target_account: target_account,
1513 reported_activity: activity,
1514 content: content,
1515 activity_ap_id: activity_ap_id,
1516 activity_with_object: activity_with_object,
1517 reporter_ap_id: reporter_ap_id,
1518 target_ap_id: target_ap_id
1519 }}
1520 end
1521
1522 test "it can create a Flag activity",
1523 %{
1524 reporter: reporter,
1525 context: context,
1526 target_account: target_account,
1527 reported_activity: reported_activity,
1528 content: content,
1529 activity_ap_id: activity_ap_id,
1530 activity_with_object: activity_with_object,
1531 reporter_ap_id: reporter_ap_id,
1532 target_ap_id: target_ap_id
1533 } do
1534 assert {:ok, activity} =
1535 ActivityPub.flag(%{
1536 actor: reporter,
1537 context: context,
1538 account: target_account,
1539 statuses: [reported_activity],
1540 content: content
1541 })
1542
1543 note_obj = %{
1544 "type" => "Note",
1545 "id" => activity_ap_id,
1546 "content" => content,
1547 "published" => activity_with_object.object.data["published"],
1548 "actor" =>
1549 AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
1550 }
1551
1552 assert %Activity{
1553 actor: ^reporter_ap_id,
1554 data: %{
1555 "type" => "Flag",
1556 "content" => ^content,
1557 "context" => ^context,
1558 "object" => [^target_ap_id, ^note_obj]
1559 }
1560 } = activity
1561 end
1562
1563 test_with_mock "strips status data from Flag, before federating it",
1564 %{
1565 reporter: reporter,
1566 context: context,
1567 target_account: target_account,
1568 reported_activity: reported_activity,
1569 content: content
1570 },
1571 Utils,
1572 [:passthrough],
1573 [] do
1574 {:ok, activity} =
1575 ActivityPub.flag(%{
1576 actor: reporter,
1577 context: context,
1578 account: target_account,
1579 statuses: [reported_activity],
1580 content: content
1581 })
1582
1583 new_data =
1584 put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]])
1585
1586 assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1587 end
1588
1589 test_with_mock "reverts on error",
1590 %{
1591 reporter: reporter,
1592 context: context,
1593 target_account: target_account,
1594 reported_activity: reported_activity,
1595 content: content
1596 },
1597 Utils,
1598 [:passthrough],
1599 maybe_federate: fn _ -> {:error, :reverted} end do
1600 assert {:error, :reverted} =
1601 ActivityPub.flag(%{
1602 actor: reporter,
1603 context: context,
1604 account: target_account,
1605 statuses: [reported_activity],
1606 content: content
1607 })
1608
1609 assert Repo.aggregate(Activity, :count, :id) == 1
1610 assert Repo.aggregate(Object, :count, :id) == 1
1611 assert Repo.aggregate(Notification, :count, :id) == 0
1612 end
1613 end
1614
1615 test "fetch_activities/2 returns activities addressed to a list " do
1616 user = insert(:user)
1617 member = insert(:user)
1618 {:ok, list} = Pleroma.List.create("foo", user)
1619 {:ok, list} = Pleroma.List.follow(list, member)
1620
1621 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1622
1623 activity = Repo.preload(activity, :bookmark)
1624 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1625
1626 assert ActivityPub.fetch_activities([], %{user: user}) == [activity]
1627 end
1628
1629 def data_uri do
1630 File.read!("test/fixtures/avatar_data_uri")
1631 end
1632
1633 describe "fetch_activities_bounded" do
1634 test "fetches private posts for followed users" do
1635 user = insert(:user)
1636
1637 {:ok, activity} =
1638 CommonAPI.post(user, %{
1639 status: "thought I looked cute might delete later :3",
1640 visibility: "private"
1641 })
1642
1643 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1644 assert result.id == activity.id
1645 end
1646
1647 test "fetches only public posts for other users" do
1648 user = insert(:user)
1649 {:ok, activity} = CommonAPI.post(user, %{status: "#cofe", visibility: "public"})
1650
1651 {:ok, _private_activity} =
1652 CommonAPI.post(user, %{
1653 status: "why is tenshi eating a corndog so cute?",
1654 visibility: "private"
1655 })
1656
1657 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1658 assert result.id == activity.id
1659 end
1660 end
1661
1662 describe "fetch_follow_information_for_user" do
1663 test "synchronizes following/followers counters" do
1664 user =
1665 insert(:user,
1666 local: false,
1667 follower_address: "http://localhost:4001/users/fuser2/followers",
1668 following_address: "http://localhost:4001/users/fuser2/following"
1669 )
1670
1671 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1672 assert info.follower_count == 527
1673 assert info.following_count == 267
1674 end
1675
1676 test "detects hidden followers" do
1677 mock(fn env ->
1678 case env.url do
1679 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1680 %Tesla.Env{status: 403, body: ""}
1681
1682 _ ->
1683 apply(HttpRequestMock, :request, [env])
1684 end
1685 end)
1686
1687 user =
1688 insert(:user,
1689 local: false,
1690 follower_address: "http://localhost:4001/users/masto_closed/followers",
1691 following_address: "http://localhost:4001/users/masto_closed/following"
1692 )
1693
1694 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1695 assert follow_info.hide_followers == true
1696 assert follow_info.hide_follows == false
1697 end
1698
1699 test "detects hidden follows" do
1700 mock(fn env ->
1701 case env.url do
1702 "http://localhost:4001/users/masto_closed/following?page=1" ->
1703 %Tesla.Env{status: 403, body: ""}
1704
1705 _ ->
1706 apply(HttpRequestMock, :request, [env])
1707 end
1708 end)
1709
1710 user =
1711 insert(:user,
1712 local: false,
1713 follower_address: "http://localhost:4001/users/masto_closed/followers",
1714 following_address: "http://localhost:4001/users/masto_closed/following"
1715 )
1716
1717 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1718 assert follow_info.hide_followers == false
1719 assert follow_info.hide_follows == true
1720 end
1721
1722 test "detects hidden follows/followers for friendica" do
1723 user =
1724 insert(:user,
1725 local: false,
1726 follower_address: "http://localhost:8080/followers/fuser3",
1727 following_address: "http://localhost:8080/following/fuser3"
1728 )
1729
1730 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1731 assert follow_info.hide_followers == true
1732 assert follow_info.follower_count == 296
1733 assert follow_info.following_count == 32
1734 assert follow_info.hide_follows == true
1735 end
1736
1737 test "doesn't crash when follower and following counters are hidden" do
1738 mock(fn env ->
1739 case env.url do
1740 "http://localhost:4001/users/masto_hidden_counters/following" ->
1741 json(
1742 %{
1743 "@context" => "https://www.w3.org/ns/activitystreams",
1744 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1745 },
1746 headers: HttpRequestMock.activitypub_object_headers()
1747 )
1748
1749 "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1750 %Tesla.Env{status: 403, body: ""}
1751
1752 "http://localhost:4001/users/masto_hidden_counters/followers" ->
1753 json(
1754 %{
1755 "@context" => "https://www.w3.org/ns/activitystreams",
1756 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1757 },
1758 headers: HttpRequestMock.activitypub_object_headers()
1759 )
1760
1761 "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1762 %Tesla.Env{status: 403, body: ""}
1763 end
1764 end)
1765
1766 user =
1767 insert(:user,
1768 local: false,
1769 follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1770 following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1771 )
1772
1773 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1774
1775 assert follow_info.hide_followers == true
1776 assert follow_info.follower_count == 0
1777 assert follow_info.hide_follows == true
1778 assert follow_info.following_count == 0
1779 end
1780 end
1781
1782 describe "fetch_favourites/3" do
1783 test "returns a favourite activities sorted by adds to favorite" do
1784 user = insert(:user)
1785 other_user = insert(:user)
1786 user1 = insert(:user)
1787 user2 = insert(:user)
1788 {:ok, a1} = CommonAPI.post(user1, %{status: "bla"})
1789 {:ok, _a2} = CommonAPI.post(user2, %{status: "traps are happy"})
1790 {:ok, a3} = CommonAPI.post(user2, %{status: "Trees Are "})
1791 {:ok, a4} = CommonAPI.post(user2, %{status: "Agent Smith "})
1792 {:ok, a5} = CommonAPI.post(user1, %{status: "Red or Blue "})
1793
1794 {:ok, _} = CommonAPI.favorite(user, a4.id)
1795 {:ok, _} = CommonAPI.favorite(other_user, a3.id)
1796 {:ok, _} = CommonAPI.favorite(user, a3.id)
1797 {:ok, _} = CommonAPI.favorite(other_user, a5.id)
1798 {:ok, _} = CommonAPI.favorite(user, a5.id)
1799 {:ok, _} = CommonAPI.favorite(other_user, a4.id)
1800 {:ok, _} = CommonAPI.favorite(user, a1.id)
1801 {:ok, _} = CommonAPI.favorite(other_user, a1.id)
1802 result = ActivityPub.fetch_favourites(user)
1803
1804 assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1805
1806 result = ActivityPub.fetch_favourites(user, %{limit: 2})
1807 assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1808 end
1809 end
1810
1811 describe "Move activity" do
1812 test "create" do
1813 %{ap_id: old_ap_id} = old_user = insert(:user)
1814 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1815 follower = insert(:user)
1816 follower_move_opted_out = insert(:user, allow_following_move: false)
1817
1818 User.follow(follower, old_user)
1819 User.follow(follower_move_opted_out, old_user)
1820
1821 assert User.following?(follower, old_user)
1822 assert User.following?(follower_move_opted_out, old_user)
1823
1824 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1825
1826 assert %Activity{
1827 actor: ^old_ap_id,
1828 data: %{
1829 "actor" => ^old_ap_id,
1830 "object" => ^old_ap_id,
1831 "target" => ^new_ap_id,
1832 "type" => "Move"
1833 },
1834 local: true,
1835 recipients: recipients
1836 } = activity
1837
1838 assert old_user.follower_address in recipients
1839
1840 params = %{
1841 "op" => "move_following",
1842 "origin_id" => old_user.id,
1843 "target_id" => new_user.id
1844 }
1845
1846 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1847
1848 Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
1849
1850 refute User.following?(follower, old_user)
1851 assert User.following?(follower, new_user)
1852
1853 assert User.following?(follower_move_opted_out, old_user)
1854 refute User.following?(follower_move_opted_out, new_user)
1855
1856 activity = %Activity{activity | object: nil}
1857
1858 assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1859
1860 assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1861 end
1862
1863 test "old user must be in the new user's `also_known_as` list" do
1864 old_user = insert(:user)
1865 new_user = insert(:user)
1866
1867 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1868 ActivityPub.move(old_user, new_user)
1869 end
1870
1871 test "do not move remote user following relationships" do
1872 %{ap_id: old_ap_id} = old_user = insert(:user)
1873 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1874 follower_remote = insert(:user, local: false)
1875
1876 User.follow(follower_remote, old_user)
1877
1878 assert User.following?(follower_remote, old_user)
1879
1880 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1881
1882 assert %Activity{
1883 actor: ^old_ap_id,
1884 data: %{
1885 "actor" => ^old_ap_id,
1886 "object" => ^old_ap_id,
1887 "target" => ^new_ap_id,
1888 "type" => "Move"
1889 },
1890 local: true
1891 } = activity
1892
1893 params = %{
1894 "op" => "move_following",
1895 "origin_id" => old_user.id,
1896 "target_id" => new_user.id
1897 }
1898
1899 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1900
1901 Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
1902
1903 assert User.following?(follower_remote, old_user)
1904 refute User.following?(follower_remote, new_user)
1905 end
1906 end
1907
1908 test "doesn't retrieve replies activities with exclude_replies" do
1909 user = insert(:user)
1910
1911 {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
1912
1913 {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id})
1914
1915 [result] = ActivityPub.fetch_public_activities(%{exclude_replies: true})
1916
1917 assert result.id == activity.id
1918
1919 assert length(ActivityPub.fetch_public_activities()) == 2
1920 end
1921
1922 describe "replies filtering with public messages" do
1923 setup :public_messages
1924
1925 test "public timeline", %{users: %{u1: user}} do
1926 activities_ids =
1927 %{}
1928 |> Map.put(:type, ["Create", "Announce"])
1929 |> Map.put(:local_only, false)
1930 |> Map.put(:blocking_user, user)
1931 |> Map.put(:muting_user, user)
1932 |> Map.put(:reply_filtering_user, user)
1933 |> ActivityPub.fetch_public_activities()
1934 |> Enum.map(& &1.id)
1935
1936 assert length(activities_ids) == 16
1937 end
1938
1939 test "public timeline with reply_visibility `following`", %{
1940 users: %{u1: user},
1941 u1: u1,
1942 u2: u2,
1943 u3: u3,
1944 u4: u4,
1945 activities: activities
1946 } do
1947 activities_ids =
1948 %{}
1949 |> Map.put(:type, ["Create", "Announce"])
1950 |> Map.put(:local_only, false)
1951 |> Map.put(:blocking_user, user)
1952 |> Map.put(:muting_user, user)
1953 |> Map.put(:reply_visibility, "following")
1954 |> Map.put(:reply_filtering_user, user)
1955 |> ActivityPub.fetch_public_activities()
1956 |> Enum.map(& &1.id)
1957
1958 assert length(activities_ids) == 14
1959
1960 visible_ids =
1961 Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
1962
1963 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1964 end
1965
1966 test "public timeline with reply_visibility `self`", %{
1967 users: %{u1: user},
1968 u1: u1,
1969 u2: u2,
1970 u3: u3,
1971 u4: u4,
1972 activities: activities
1973 } do
1974 activities_ids =
1975 %{}
1976 |> Map.put(:type, ["Create", "Announce"])
1977 |> Map.put(:local_only, false)
1978 |> Map.put(:blocking_user, user)
1979 |> Map.put(:muting_user, user)
1980 |> Map.put(:reply_visibility, "self")
1981 |> Map.put(:reply_filtering_user, user)
1982 |> ActivityPub.fetch_public_activities()
1983 |> Enum.map(& &1.id)
1984
1985 assert length(activities_ids) == 10
1986 visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
1987 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1988 end
1989
1990 test "home timeline", %{
1991 users: %{u1: user},
1992 activities: activities,
1993 u1: u1,
1994 u2: u2,
1995 u3: u3,
1996 u4: u4
1997 } do
1998 params =
1999 %{}
2000 |> Map.put(:type, ["Create", "Announce"])
2001 |> Map.put(:blocking_user, user)
2002 |> Map.put(:muting_user, user)
2003 |> Map.put(:user, user)
2004 |> Map.put(:reply_filtering_user, user)
2005
2006 activities_ids =
2007 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2008 |> Enum.map(& &1.id)
2009
2010 assert length(activities_ids) == 13
2011
2012 visible_ids =
2013 Map.values(u1) ++
2014 Map.values(u3) ++
2015 [
2016 activities[:a1],
2017 activities[:a2],
2018 activities[:a4],
2019 u2[:r1],
2020 u2[:r3],
2021 u4[:r1],
2022 u4[:r2]
2023 ]
2024
2025 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2026 end
2027
2028 test "home timeline with reply_visibility `following`", %{
2029 users: %{u1: user},
2030 activities: activities,
2031 u1: u1,
2032 u2: u2,
2033 u3: u3,
2034 u4: u4
2035 } do
2036 params =
2037 %{}
2038 |> Map.put(:type, ["Create", "Announce"])
2039 |> Map.put(:blocking_user, user)
2040 |> Map.put(:muting_user, user)
2041 |> Map.put(:user, user)
2042 |> Map.put(:reply_visibility, "following")
2043 |> Map.put(:reply_filtering_user, user)
2044
2045 activities_ids =
2046 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2047 |> Enum.map(& &1.id)
2048
2049 assert length(activities_ids) == 11
2050
2051 visible_ids =
2052 Map.values(u1) ++
2053 [
2054 activities[:a1],
2055 activities[:a2],
2056 activities[:a4],
2057 u2[:r1],
2058 u2[:r3],
2059 u3[:r1],
2060 u4[:r1],
2061 u4[:r2]
2062 ]
2063
2064 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2065 end
2066
2067 test "home timeline with reply_visibility `self`", %{
2068 users: %{u1: user},
2069 activities: activities,
2070 u1: u1,
2071 u2: u2,
2072 u3: u3,
2073 u4: u4
2074 } do
2075 params =
2076 %{}
2077 |> Map.put(:type, ["Create", "Announce"])
2078 |> Map.put(:blocking_user, user)
2079 |> Map.put(:muting_user, user)
2080 |> Map.put(:user, user)
2081 |> Map.put(:reply_visibility, "self")
2082 |> Map.put(:reply_filtering_user, user)
2083
2084 activities_ids =
2085 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2086 |> Enum.map(& &1.id)
2087
2088 assert length(activities_ids) == 9
2089
2090 visible_ids =
2091 Map.values(u1) ++
2092 [
2093 activities[:a1],
2094 activities[:a2],
2095 activities[:a4],
2096 u2[:r1],
2097 u3[:r1],
2098 u4[:r1]
2099 ]
2100
2101 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2102 end
2103
2104 test "filtering out announces where the user is the actor of the announced message" do
2105 user = insert(:user)
2106 other_user = insert(:user)
2107 third_user = insert(:user)
2108 User.follow(user, other_user)
2109
2110 {:ok, post} = CommonAPI.post(user, %{status: "yo"})
2111 {:ok, other_post} = CommonAPI.post(third_user, %{status: "yo"})
2112 {:ok, _announce} = CommonAPI.repeat(post.id, other_user)
2113 {:ok, _announce} = CommonAPI.repeat(post.id, third_user)
2114 {:ok, announce} = CommonAPI.repeat(other_post.id, other_user)
2115
2116 params = %{
2117 type: ["Announce"]
2118 }
2119
2120 results =
2121 [user.ap_id | User.following(user)]
2122 |> ActivityPub.fetch_activities(params)
2123
2124 assert length(results) == 3
2125
2126 params = %{
2127 type: ["Announce"],
2128 announce_filtering_user: user
2129 }
2130
2131 [result] =
2132 [user.ap_id | User.following(user)]
2133 |> ActivityPub.fetch_activities(params)
2134
2135 assert result.id == announce.id
2136 end
2137 end
2138
2139 describe "replies filtering with private messages" do
2140 setup :private_messages
2141
2142 test "public timeline", %{users: %{u1: user}} do
2143 activities_ids =
2144 %{}
2145 |> Map.put(:type, ["Create", "Announce"])
2146 |> Map.put(:local_only, false)
2147 |> Map.put(:blocking_user, user)
2148 |> Map.put(:muting_user, user)
2149 |> Map.put(:user, user)
2150 |> ActivityPub.fetch_public_activities()
2151 |> Enum.map(& &1.id)
2152
2153 assert activities_ids == []
2154 end
2155
2156 test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2157 activities_ids =
2158 %{}
2159 |> Map.put(:type, ["Create", "Announce"])
2160 |> Map.put(:local_only, false)
2161 |> Map.put(:blocking_user, user)
2162 |> Map.put(:muting_user, user)
2163 |> Map.put(:reply_visibility, "following")
2164 |> Map.put(:reply_filtering_user, user)
2165 |> Map.put(:user, user)
2166 |> ActivityPub.fetch_public_activities()
2167 |> Enum.map(& &1.id)
2168
2169 assert activities_ids == []
2170 end
2171
2172 test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
2173 activities_ids =
2174 %{}
2175 |> Map.put(:type, ["Create", "Announce"])
2176 |> Map.put(:local_only, false)
2177 |> Map.put(:blocking_user, user)
2178 |> Map.put(:muting_user, user)
2179 |> Map.put(:reply_visibility, "self")
2180 |> Map.put(:reply_filtering_user, user)
2181 |> Map.put(:user, user)
2182 |> ActivityPub.fetch_public_activities()
2183 |> Enum.map(& &1.id)
2184
2185 assert activities_ids == []
2186
2187 activities_ids =
2188 %{}
2189 |> Map.put(:reply_visibility, "self")
2190 |> Map.put(:reply_filtering_user, nil)
2191 |> ActivityPub.fetch_public_activities()
2192
2193 assert activities_ids == []
2194 end
2195
2196 test "home timeline", %{users: %{u1: user}} do
2197 params =
2198 %{}
2199 |> Map.put(:type, ["Create", "Announce"])
2200 |> Map.put(:blocking_user, user)
2201 |> Map.put(:muting_user, user)
2202 |> Map.put(:user, user)
2203
2204 activities_ids =
2205 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2206 |> Enum.map(& &1.id)
2207
2208 assert length(activities_ids) == 12
2209 end
2210
2211 test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2212 params =
2213 %{}
2214 |> Map.put(:type, ["Create", "Announce"])
2215 |> Map.put(:blocking_user, user)
2216 |> Map.put(:muting_user, user)
2217 |> Map.put(:user, user)
2218 |> Map.put(:reply_visibility, "following")
2219 |> Map.put(:reply_filtering_user, user)
2220
2221 activities_ids =
2222 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2223 |> Enum.map(& &1.id)
2224
2225 assert length(activities_ids) == 12
2226 end
2227
2228 test "home timeline with default reply_visibility `self`", %{
2229 users: %{u1: user},
2230 activities: activities,
2231 u1: u1,
2232 u2: u2,
2233 u3: u3,
2234 u4: u4
2235 } do
2236 params =
2237 %{}
2238 |> Map.put(:type, ["Create", "Announce"])
2239 |> Map.put(:blocking_user, user)
2240 |> Map.put(:muting_user, user)
2241 |> Map.put(:user, user)
2242 |> Map.put(:reply_visibility, "self")
2243 |> Map.put(:reply_filtering_user, user)
2244
2245 activities_ids =
2246 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2247 |> Enum.map(& &1.id)
2248
2249 assert length(activities_ids) == 10
2250
2251 visible_ids =
2252 Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
2253
2254 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2255 end
2256 end
2257
2258 defp public_messages(_) do
2259 [u1, u2, u3, u4] = insert_list(4, :user)
2260 {:ok, u1, u2} = User.follow(u1, u2)
2261 {:ok, u2, u1} = User.follow(u2, u1)
2262 {:ok, u1, u4} = User.follow(u1, u4)
2263 {:ok, u4, u1} = User.follow(u4, u1)
2264
2265 {:ok, u2, u3} = User.follow(u2, u3)
2266 {:ok, u3, u2} = User.follow(u3, u2)
2267
2268 {:ok, a1} = CommonAPI.post(u1, %{status: "Status"})
2269
2270 {:ok, r1_1} =
2271 CommonAPI.post(u2, %{
2272 status: "@#{u1.nickname} reply from u2 to u1",
2273 in_reply_to_status_id: a1.id
2274 })
2275
2276 {:ok, r1_2} =
2277 CommonAPI.post(u3, %{
2278 status: "@#{u1.nickname} reply from u3 to u1",
2279 in_reply_to_status_id: a1.id
2280 })
2281
2282 {:ok, r1_3} =
2283 CommonAPI.post(u4, %{
2284 status: "@#{u1.nickname} reply from u4 to u1",
2285 in_reply_to_status_id: a1.id
2286 })
2287
2288 {:ok, a2} = CommonAPI.post(u2, %{status: "Status"})
2289
2290 {:ok, r2_1} =
2291 CommonAPI.post(u1, %{
2292 status: "@#{u2.nickname} reply from u1 to u2",
2293 in_reply_to_status_id: a2.id
2294 })
2295
2296 {:ok, r2_2} =
2297 CommonAPI.post(u3, %{
2298 status: "@#{u2.nickname} reply from u3 to u2",
2299 in_reply_to_status_id: a2.id
2300 })
2301
2302 {:ok, r2_3} =
2303 CommonAPI.post(u4, %{
2304 status: "@#{u2.nickname} reply from u4 to u2",
2305 in_reply_to_status_id: a2.id
2306 })
2307
2308 {:ok, a3} = CommonAPI.post(u3, %{status: "Status"})
2309
2310 {:ok, r3_1} =
2311 CommonAPI.post(u1, %{
2312 status: "@#{u3.nickname} reply from u1 to u3",
2313 in_reply_to_status_id: a3.id
2314 })
2315
2316 {:ok, r3_2} =
2317 CommonAPI.post(u2, %{
2318 status: "@#{u3.nickname} reply from u2 to u3",
2319 in_reply_to_status_id: a3.id
2320 })
2321
2322 {:ok, r3_3} =
2323 CommonAPI.post(u4, %{
2324 status: "@#{u3.nickname} reply from u4 to u3",
2325 in_reply_to_status_id: a3.id
2326 })
2327
2328 {:ok, a4} = CommonAPI.post(u4, %{status: "Status"})
2329
2330 {:ok, r4_1} =
2331 CommonAPI.post(u1, %{
2332 status: "@#{u4.nickname} reply from u1 to u4",
2333 in_reply_to_status_id: a4.id
2334 })
2335
2336 {:ok, r4_2} =
2337 CommonAPI.post(u2, %{
2338 status: "@#{u4.nickname} reply from u2 to u4",
2339 in_reply_to_status_id: a4.id
2340 })
2341
2342 {:ok, r4_3} =
2343 CommonAPI.post(u3, %{
2344 status: "@#{u4.nickname} reply from u3 to u4",
2345 in_reply_to_status_id: a4.id
2346 })
2347
2348 {:ok,
2349 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2350 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2351 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2352 u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
2353 u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
2354 u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
2355 end
2356
2357 defp private_messages(_) do
2358 [u1, u2, u3, u4] = insert_list(4, :user)
2359 {:ok, u1, u2} = User.follow(u1, u2)
2360 {:ok, u2, u1} = User.follow(u2, u1)
2361 {:ok, u1, u3} = User.follow(u1, u3)
2362 {:ok, u3, u1} = User.follow(u3, u1)
2363 {:ok, u1, u4} = User.follow(u1, u4)
2364 {:ok, u4, u1} = User.follow(u4, u1)
2365
2366 {:ok, u2, u3} = User.follow(u2, u3)
2367 {:ok, u3, u2} = User.follow(u3, u2)
2368
2369 {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"})
2370
2371 {:ok, r1_1} =
2372 CommonAPI.post(u2, %{
2373 status: "@#{u1.nickname} reply from u2 to u1",
2374 in_reply_to_status_id: a1.id,
2375 visibility: "private"
2376 })
2377
2378 {:ok, r1_2} =
2379 CommonAPI.post(u3, %{
2380 status: "@#{u1.nickname} reply from u3 to u1",
2381 in_reply_to_status_id: a1.id,
2382 visibility: "private"
2383 })
2384
2385 {:ok, r1_3} =
2386 CommonAPI.post(u4, %{
2387 status: "@#{u1.nickname} reply from u4 to u1",
2388 in_reply_to_status_id: a1.id,
2389 visibility: "private"
2390 })
2391
2392 {:ok, a2} = CommonAPI.post(u2, %{status: "Status", visibility: "private"})
2393
2394 {:ok, r2_1} =
2395 CommonAPI.post(u1, %{
2396 status: "@#{u2.nickname} reply from u1 to u2",
2397 in_reply_to_status_id: a2.id,
2398 visibility: "private"
2399 })
2400
2401 {:ok, r2_2} =
2402 CommonAPI.post(u3, %{
2403 status: "@#{u2.nickname} reply from u3 to u2",
2404 in_reply_to_status_id: a2.id,
2405 visibility: "private"
2406 })
2407
2408 {:ok, a3} = CommonAPI.post(u3, %{status: "Status", visibility: "private"})
2409
2410 {:ok, r3_1} =
2411 CommonAPI.post(u1, %{
2412 status: "@#{u3.nickname} reply from u1 to u3",
2413 in_reply_to_status_id: a3.id,
2414 visibility: "private"
2415 })
2416
2417 {:ok, r3_2} =
2418 CommonAPI.post(u2, %{
2419 status: "@#{u3.nickname} reply from u2 to u3",
2420 in_reply_to_status_id: a3.id,
2421 visibility: "private"
2422 })
2423
2424 {:ok, a4} = CommonAPI.post(u4, %{status: "Status", visibility: "private"})
2425
2426 {:ok, r4_1} =
2427 CommonAPI.post(u1, %{
2428 status: "@#{u4.nickname} reply from u1 to u4",
2429 in_reply_to_status_id: a4.id,
2430 visibility: "private"
2431 })
2432
2433 {:ok,
2434 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2435 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2436 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2437 u2: %{r1: r2_1.id, r2: r2_2.id},
2438 u3: %{r1: r3_1.id, r2: r3_2.id},
2439 u4: %{r1: r4_1.id}}
2440 end
2441
2442 describe "maybe_update_follow_information/1" do
2443 setup do
2444 clear_config([:instance, :external_user_synchronization], true)
2445
2446 user = %{
2447 local: false,
2448 ap_id: "https://gensokyo.2hu/users/raymoo",
2449 following_address: "https://gensokyo.2hu/users/following",
2450 follower_address: "https://gensokyo.2hu/users/followers",
2451 type: "Person"
2452 }
2453
2454 %{user: user}
2455 end
2456
2457 test "logs an error when it can't fetch the info", %{user: user} do
2458 assert capture_log(fn ->
2459 ActivityPub.maybe_update_follow_information(user)
2460 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2461 end
2462
2463 test "just returns the input if the user type is Application", %{
2464 user: user
2465 } do
2466 user =
2467 user
2468 |> Map.put(:type, "Application")
2469
2470 refute capture_log(fn ->
2471 assert ^user = ActivityPub.maybe_update_follow_information(user)
2472 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2473 end
2474
2475 test "it just returns the input if the user has no following/follower addresses", %{
2476 user: user
2477 } do
2478 user =
2479 user
2480 |> Map.put(:following_address, nil)
2481 |> Map.put(:follower_address, nil)
2482
2483 refute capture_log(fn ->
2484 assert ^user = ActivityPub.maybe_update_follow_information(user)
2485 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2486 end
2487 end
2488
2489 describe "global activity expiration" do
2490 test "creates an activity expiration for local Create activities" do
2491 clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy)
2492
2493 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
2494 {:ok, follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
2495
2496 assert_enqueued(
2497 worker: Pleroma.Workers.PurgeExpiredActivity,
2498 args: %{activity_id: activity.id},
2499 scheduled_at:
2500 activity.inserted_at
2501 |> DateTime.from_naive!("Etc/UTC")
2502 |> Timex.shift(days: 365)
2503 )
2504
2505 refute_enqueued(
2506 worker: Pleroma.Workers.PurgeExpiredActivity,
2507 args: %{activity_id: follow.id}
2508 )
2509 end
2510 end
2511
2512 describe "handling of clashing nicknames" do
2513 test "renames an existing user with a clashing nickname and a different ap id" do
2514 orig_user =
2515 insert(
2516 :user,
2517 local: false,
2518 nickname: "admin@mastodon.example.org",
2519 ap_id: "http://mastodon.example.org/users/harinezumigari"
2520 )
2521
2522 %{
2523 nickname: orig_user.nickname,
2524 ap_id: orig_user.ap_id <> "part_2"
2525 }
2526 |> ActivityPub.maybe_handle_clashing_nickname()
2527
2528 user = User.get_by_id(orig_user.id)
2529
2530 assert user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
2531 end
2532
2533 test "does nothing with a clashing nickname and the same ap id" do
2534 orig_user =
2535 insert(
2536 :user,
2537 local: false,
2538 nickname: "admin@mastodon.example.org",
2539 ap_id: "http://mastodon.example.org/users/harinezumigari"
2540 )
2541
2542 %{
2543 nickname: orig_user.nickname,
2544 ap_id: orig_user.ap_id
2545 }
2546 |> ActivityPub.maybe_handle_clashing_nickname()
2547
2548 user = User.get_by_id(orig_user.id)
2549
2550 assert user.nickname == orig_user.nickname
2551 end
2552 end
2553
2554 describe "reply filtering" do
2555 test "`following` still contains announcements by friends" do
2556 user = insert(:user)
2557 followed = insert(:user)
2558 not_followed = insert(:user)
2559
2560 User.follow(user, followed)
2561
2562 {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2563
2564 {:ok, not_followed_to_followed} =
2565 CommonAPI.post(not_followed, %{
2566 status: "Also hello",
2567 in_reply_to_status_id: followed_post.id
2568 })
2569
2570 {:ok, retoot} = CommonAPI.repeat(not_followed_to_followed.id, followed)
2571
2572 params =
2573 %{}
2574 |> Map.put(:type, ["Create", "Announce"])
2575 |> Map.put(:blocking_user, user)
2576 |> Map.put(:muting_user, user)
2577 |> Map.put(:reply_filtering_user, user)
2578 |> Map.put(:reply_visibility, "following")
2579 |> Map.put(:announce_filtering_user, user)
2580 |> Map.put(:user, user)
2581
2582 activities =
2583 [user.ap_id | User.following(user)]
2584 |> ActivityPub.fetch_activities(params)
2585
2586 followed_post_id = followed_post.id
2587 retoot_id = retoot.id
2588
2589 assert [%{id: ^followed_post_id}, %{id: ^retoot_id}] = activities
2590
2591 assert length(activities) == 2
2592 end
2593
2594 # This test is skipped because, while this is the desired behavior,
2595 # there seems to be no good way to achieve it with the method that
2596 # we currently use for detecting to who a reply is directed.
2597 # This is a TODO and should be fixed by a later rewrite of the code
2598 # in question.
2599 @tag skip: true
2600 test "`following` still contains self-replies by friends" do
2601 user = insert(:user)
2602 followed = insert(:user)
2603 not_followed = insert(:user)
2604
2605 User.follow(user, followed)
2606
2607 {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2608 {:ok, not_followed_post} = CommonAPI.post(not_followed, %{status: "Also hello"})
2609
2610 {:ok, _followed_to_not_followed} =
2611 CommonAPI.post(followed, %{status: "sup", in_reply_to_status_id: not_followed_post.id})
2612
2613 {:ok, _followed_self_reply} =
2614 CommonAPI.post(followed, %{status: "Also cofe", in_reply_to_status_id: followed_post.id})
2615
2616 params =
2617 %{}
2618 |> Map.put(:type, ["Create", "Announce"])
2619 |> Map.put(:blocking_user, user)
2620 |> Map.put(:muting_user, user)
2621 |> Map.put(:reply_filtering_user, user)
2622 |> Map.put(:reply_visibility, "following")
2623 |> Map.put(:announce_filtering_user, user)
2624 |> Map.put(:user, user)
2625
2626 activities =
2627 [user.ap_id | User.following(user)]
2628 |> ActivityPub.fetch_activities(params)
2629
2630 assert length(activities) == 2
2631 end
2632 end
2633
2634 test "allow fetching of accounts with an empty string name field" do
2635 Tesla.Mock.mock(fn
2636 %{method: :get, url: "https://princess.cat/users/mewmew"} ->
2637 file = File.read!("test/fixtures/mewmew_no_name.json")
2638 %Tesla.Env{status: 200, body: file, headers: HttpRequestMock.activitypub_object_headers()}
2639 end)
2640
2641 {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew")
2642 assert user.name == " "
2643 end
2644
2645 describe "persist/1" do
2646 test "should not persist remote delete activities" do
2647 poster = insert(:user, local: false)
2648 {:ok, post} = CommonAPI.post(poster, %{status: "hhhhhh"})
2649
2650 {:ok, delete_data, meta} = Builder.delete(poster, post)
2651 local_opts = Keyword.put(meta, :local, false)
2652 {:ok, act, _meta} = ActivityPub.persist(delete_data, local_opts)
2653 refute act.inserted_at
2654 end
2655
2656 test "should not persist remote undo activities" do
2657 poster = insert(:user, local: false)
2658 liker = insert(:user, local: false)
2659 {:ok, post} = CommonAPI.post(poster, %{status: "hhhhhh"})
2660 {:ok, like} = CommonAPI.favorite(liker, post.id)
2661
2662 {:ok, undo_data, meta} = Builder.undo(liker, like)
2663 local_opts = Keyword.put(meta, :local, false)
2664 {:ok, act, _meta} = ActivityPub.persist(undo_data, local_opts)
2665 refute act.inserted_at
2666 end
2667 end
2668 end