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