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