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