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