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