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