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