Remerge of hashtag following (#341)
[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} = CommonAPI.post(other_user, %{status: "hello :)", visibility: "public"})
729 {:ok, public} = CommonAPI.post(user, %{status: "maji #tenshi", visibility: "public"})
730 {:ok, _unrelated} = CommonAPI.post(user, %{status: "dai #tensh", visibility: "public"})
731 {:ok, unlisted} = CommonAPI.post(user, %{status: "maji #tenshi", visibility: "unlisted"})
732 {:ok, _private} = CommonAPI.post(user, %{status: "maji #tenshi", visibility: "private"})
733
734 activities = ActivityPub.fetch_activities([other_user.follower_address], %{followed_hashtags: [hashtag.id]})
735 assert length(activities) == 3
736 normal_id = normally_visible.id
737 public_id = public.id
738 unlisted_id = unlisted.id
739 assert [%{id: ^normal_id}, %{id: ^public_id}, %{id: ^unlisted_id}] = activities
740 end
741 end
742
743 describe "fetch activities in context" do
744 test "retrieves activities that have a given context" do
745 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
746 {:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
747 {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
748 {:ok, _activity_four} = ActivityBuilder.insert(%{"type" => "Announce", "context" => "2hu"})
749 activity_five = insert(:note_activity)
750 user = insert(:user)
751
752 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})
753
754 activities = ActivityPub.fetch_activities_for_context("2hu", %{blocking_user: user})
755 assert activities == [activity_two, activity]
756 end
757
758 test "doesn't return activities with filtered words" do
759 user = insert(:user)
760 user_two = insert(:user)
761 insert(:filter, user: user, phrase: "test", hide: true)
762
763 {:ok, %{id: id1, data: %{"context" => context}}} = CommonAPI.post(user, %{status: "1"})
764
765 {:ok, %{id: id2}} = CommonAPI.post(user_two, %{status: "2", in_reply_to_status_id: id1})
766
767 {:ok, %{id: id3} = user_activity} =
768 CommonAPI.post(user, %{status: "3 test?", in_reply_to_status_id: id2})
769
770 {:ok, %{id: id4} = filtered_activity} =
771 CommonAPI.post(user_two, %{status: "4 test!", in_reply_to_status_id: id3})
772
773 {:ok, _} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
774
775 activities =
776 context
777 |> ActivityPub.fetch_activities_for_context(%{user: user})
778 |> Enum.map(& &1.id)
779
780 assert length(activities) == 4
781 assert user_activity.id in activities
782 refute filtered_activity.id in activities
783 end
784 end
785
786 test "doesn't return blocked activities" do
787 activity_one = insert(:note_activity)
788 activity_two = insert(:note_activity)
789 activity_three = insert(:note_activity)
790 user = insert(:user)
791 booster = insert(:user)
792 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
793
794 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
795
796 assert Enum.member?(activities, activity_two)
797 assert Enum.member?(activities, activity_three)
798 refute Enum.member?(activities, activity_one)
799
800 {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
801
802 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
803
804 assert Enum.member?(activities, activity_two)
805 assert Enum.member?(activities, activity_three)
806 assert Enum.member?(activities, activity_one)
807
808 {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})
809 {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
810 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
811 activity_three = Activity.get_by_id(activity_three.id)
812
813 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
814
815 assert Enum.member?(activities, activity_two)
816 refute Enum.member?(activities, activity_three)
817 refute Enum.member?(activities, boost_activity)
818 assert Enum.member?(activities, activity_one)
819
820 activities = ActivityPub.fetch_activities([], %{blocking_user: nil, skip_preload: true})
821
822 assert Enum.member?(activities, activity_two)
823 assert Enum.member?(activities, activity_three)
824 assert Enum.member?(activities, boost_activity)
825 assert Enum.member?(activities, activity_one)
826 end
827
828 test "doesn't return activities from deactivated users" do
829 _user = insert(:user)
830 deactivated = insert(:user)
831 active = insert(:user)
832 {:ok, activity_one} = CommonAPI.post(deactivated, %{status: "hey!"})
833 {:ok, activity_two} = CommonAPI.post(active, %{status: "yay!"})
834 {:ok, _updated_user} = User.set_activation(deactivated, false)
835
836 activities = ActivityPub.fetch_activities([], %{})
837
838 refute Enum.member?(activities, activity_one)
839 assert Enum.member?(activities, activity_two)
840 end
841
842 test "always see your own posts even when they address people you block" do
843 user = insert(:user)
844 blockee = insert(:user)
845
846 {:ok, _} = User.block(user, blockee)
847 {:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{blockee.nickname}"})
848
849 activities = ActivityPub.fetch_activities([], %{blocking_user: user})
850
851 assert Enum.member?(activities, activity)
852 end
853
854 test "doesn't return transitive interactions concerning blocked users" do
855 blocker = insert(:user)
856 blockee = insert(:user)
857 friend = insert(:user)
858
859 {:ok, _user_relationship} = User.block(blocker, blockee)
860
861 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
862
863 {:ok, activity_two} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
864
865 {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
866
867 {:ok, activity_four} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
868
869 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker})
870
871 assert Enum.member?(activities, activity_one)
872 refute Enum.member?(activities, activity_two)
873 refute Enum.member?(activities, activity_three)
874 refute Enum.member?(activities, activity_four)
875 end
876
877 test "doesn't return announce activities with blocked users in 'to'" do
878 blocker = insert(:user)
879 blockee = insert(:user)
880 friend = insert(:user)
881
882 {:ok, _user_relationship} = User.block(blocker, blockee)
883
884 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
885
886 {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
887
888 {:ok, activity_three} = CommonAPI.repeat(activity_two.id, friend)
889
890 activities =
891 ActivityPub.fetch_activities([], %{blocking_user: blocker})
892 |> Enum.map(fn act -> act.id end)
893
894 assert Enum.member?(activities, activity_one.id)
895 refute Enum.member?(activities, activity_two.id)
896 refute Enum.member?(activities, activity_three.id)
897 end
898
899 test "doesn't return announce activities with blocked users in 'cc'" do
900 blocker = insert(:user)
901 blockee = insert(:user)
902 friend = insert(:user)
903
904 {:ok, _user_relationship} = User.block(blocker, blockee)
905
906 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
907
908 {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
909
910 assert object = Pleroma.Object.normalize(activity_two, fetch: false)
911
912 data = %{
913 "actor" => friend.ap_id,
914 "object" => object.data["id"],
915 "context" => object.data["context"],
916 "type" => "Announce",
917 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
918 "cc" => [blockee.ap_id]
919 }
920
921 assert {:ok, activity_three} = ActivityPub.insert(data)
922
923 activities =
924 ActivityPub.fetch_activities([], %{blocking_user: blocker})
925 |> Enum.map(fn act -> act.id end)
926
927 assert Enum.member?(activities, activity_one.id)
928 refute Enum.member?(activities, activity_two.id)
929 refute Enum.member?(activities, activity_three.id)
930 end
931
932 test "doesn't return activities from blocked domains" do
933 domain = "dogwhistle.zone"
934 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
935 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
936 activity = insert(:note_activity, %{note: note})
937 user = insert(:user)
938 {:ok, user} = User.block_domain(user, domain)
939
940 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
941
942 refute activity in activities
943
944 followed_user = insert(:user)
945 CommonAPI.follow(user, followed_user)
946 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user)
947
948 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
949
950 refute repeat_activity in activities
951 end
952
953 test "see your own posts even when they adress actors from blocked domains" do
954 user = insert(:user)
955
956 domain = "dogwhistle.zone"
957 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
958
959 {:ok, user} = User.block_domain(user, domain)
960
961 {:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{domain_user.nickname}"})
962
963 activities = ActivityPub.fetch_activities([], %{blocking_user: user})
964
965 assert Enum.member?(activities, activity)
966 end
967
968 test "does return activities from followed users on blocked domains" do
969 domain = "meanies.social"
970 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
971 blocker = insert(:user)
972
973 {:ok, blocker, domain_user} = User.follow(blocker, domain_user)
974 {:ok, blocker} = User.block_domain(blocker, domain)
975
976 assert User.following?(blocker, domain_user)
977 assert User.blocks_domain?(blocker, domain_user)
978 refute User.blocks?(blocker, domain_user)
979
980 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
981 activity = insert(:note_activity, %{note: note})
982
983 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
984
985 assert activity in activities
986
987 # And check that if the guy we DO follow boosts someone else from their domain,
988 # that should be hidden
989 another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"})
990 bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}})
991 bad_activity = insert(:note_activity, %{note: bad_note})
992 {:ok, repeat_activity} = CommonAPI.repeat(bad_activity.id, domain_user)
993
994 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
995
996 refute repeat_activity in activities
997 end
998
999 test "returns your own posts regardless of mute" do
1000 user = insert(:user)
1001 muted = insert(:user)
1002
1003 {:ok, muted_post} = CommonAPI.post(muted, %{status: "Im stupid"})
1004
1005 {:ok, reply} =
1006 CommonAPI.post(user, %{status: "I'm muting you", in_reply_to_status_id: muted_post.id})
1007
1008 {:ok, _} = User.mute(user, muted)
1009
1010 [activity] = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
1011
1012 assert activity.id == reply.id
1013 end
1014
1015 test "doesn't return muted activities" do
1016 activity_one = insert(:note_activity)
1017 activity_two = insert(:note_activity)
1018 activity_three = insert(:note_activity)
1019 user = insert(:user)
1020 booster = insert(:user)
1021
1022 activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
1023 {:ok, _user_relationships} = User.mute(user, activity_one_actor)
1024
1025 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
1026
1027 assert Enum.member?(activities, activity_two)
1028 assert Enum.member?(activities, activity_three)
1029 refute Enum.member?(activities, activity_one)
1030
1031 # Calling with 'with_muted' will deliver muted activities, too.
1032 activities =
1033 ActivityPub.fetch_activities([], %{
1034 muting_user: user,
1035 with_muted: true,
1036 skip_preload: true
1037 })
1038
1039 assert Enum.member?(activities, activity_two)
1040 assert Enum.member?(activities, activity_three)
1041 assert Enum.member?(activities, activity_one)
1042
1043 {:ok, _user_mute} = User.unmute(user, activity_one_actor)
1044
1045 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
1046
1047 assert Enum.member?(activities, activity_two)
1048 assert Enum.member?(activities, activity_three)
1049 assert Enum.member?(activities, activity_one)
1050
1051 activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
1052 {:ok, _user_relationships} = User.mute(user, activity_three_actor)
1053 {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
1054 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
1055 activity_three = Activity.get_by_id(activity_three.id)
1056
1057 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
1058
1059 assert Enum.member?(activities, activity_two)
1060 refute Enum.member?(activities, activity_three)
1061 refute Enum.member?(activities, boost_activity)
1062 assert Enum.member?(activities, activity_one)
1063
1064 activities = ActivityPub.fetch_activities([], %{muting_user: nil, skip_preload: true})
1065
1066 assert Enum.member?(activities, activity_two)
1067 assert Enum.member?(activities, activity_three)
1068 assert Enum.member?(activities, boost_activity)
1069 assert Enum.member?(activities, activity_one)
1070 end
1071
1072 test "doesn't return thread muted activities" do
1073 user = insert(:user)
1074 _activity_one = insert(:note_activity)
1075 note_two = insert(:note, data: %{"context" => "suya.."})
1076 activity_two = insert(:note_activity, note: note_two)
1077
1078 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
1079
1080 assert [_activity_one] = ActivityPub.fetch_activities([], %{muting_user: user})
1081 end
1082
1083 test "returns thread muted activities when with_muted is set" do
1084 user = insert(:user)
1085 _activity_one = insert(:note_activity)
1086 note_two = insert(:note, data: %{"context" => "suya.."})
1087 activity_two = insert(:note_activity, note: note_two)
1088
1089 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
1090
1091 assert [_activity_two, _activity_one] =
1092 ActivityPub.fetch_activities([], %{muting_user: user, with_muted: true})
1093 end
1094
1095 test "does include announces on request" do
1096 activity_three = insert(:note_activity)
1097 user = insert(:user)
1098 booster = insert(:user)
1099
1100 {:ok, user, booster} = User.follow(user, booster)
1101
1102 {:ok, announce} = CommonAPI.repeat(activity_three.id, booster)
1103
1104 [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])
1105
1106 assert announce_activity.id == announce.id
1107 end
1108
1109 test "excludes reblogs on request" do
1110 user = insert(:user)
1111 {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
1112 {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
1113
1114 [activity] = ActivityPub.fetch_user_activities(user, nil, %{exclude_reblogs: true})
1115
1116 assert activity == expected_activity
1117 end
1118
1119 describe "irreversible filters" do
1120 setup do
1121 user = insert(:user)
1122 user_two = insert(:user)
1123
1124 insert(:filter, user: user_two, phrase: "cofe", hide: true)
1125 insert(:filter, user: user_two, phrase: "ok boomer", hide: true)
1126 insert(:filter, user: user_two, phrase: "test", hide: false)
1127
1128 params = %{
1129 type: ["Create", "Announce"],
1130 user: user_two
1131 }
1132
1133 {:ok, %{user: user, user_two: user_two, params: params}}
1134 end
1135
1136 test "it returns statuses if they don't contain exact filter words", %{
1137 user: user,
1138 params: params
1139 } do
1140 {:ok, _} = CommonAPI.post(user, %{status: "hey"})
1141 {:ok, _} = CommonAPI.post(user, %{status: "got cofefe?"})
1142 {:ok, _} = CommonAPI.post(user, %{status: "I am not a boomer"})
1143 {:ok, _} = CommonAPI.post(user, %{status: "ok boomers"})
1144 {:ok, _} = CommonAPI.post(user, %{status: "ccofee is not a word"})
1145 {:ok, _} = CommonAPI.post(user, %{status: "this is a test"})
1146
1147 activities = ActivityPub.fetch_activities([], params)
1148
1149 assert Enum.count(activities) == 6
1150 end
1151
1152 test "it does not filter user's own statuses", %{user_two: user_two, params: params} do
1153 {:ok, _} = CommonAPI.post(user_two, %{status: "Give me some cofe!"})
1154 {:ok, _} = CommonAPI.post(user_two, %{status: "ok boomer"})
1155
1156 activities = ActivityPub.fetch_activities([], params)
1157
1158 assert Enum.count(activities) == 2
1159 end
1160
1161 test "it excludes statuses with filter words", %{user: user, params: params} do
1162 {:ok, _} = CommonAPI.post(user, %{status: "Give me some cofe!"})
1163 {:ok, _} = CommonAPI.post(user, %{status: "ok boomer"})
1164 {:ok, _} = CommonAPI.post(user, %{status: "is it a cOfE?"})
1165 {:ok, _} = CommonAPI.post(user, %{status: "cofe is all I need"})
1166 {:ok, _} = CommonAPI.post(user, %{status: "— ok BOOMER\n"})
1167
1168 activities = ActivityPub.fetch_activities([], params)
1169
1170 assert Enum.empty?(activities)
1171 end
1172
1173 test "it returns all statuses if user does not have any filters" do
1174 another_user = insert(:user)
1175 {:ok, _} = CommonAPI.post(another_user, %{status: "got cofe?"})
1176 {:ok, _} = CommonAPI.post(another_user, %{status: "test!"})
1177
1178 activities =
1179 ActivityPub.fetch_activities([], %{
1180 type: ["Create", "Announce"],
1181 user: another_user
1182 })
1183
1184 assert Enum.count(activities) == 2
1185 end
1186 end
1187
1188 describe "public fetch activities" do
1189 test "doesn't retrieve unlisted activities" do
1190 user = insert(:user)
1191
1192 {:ok, _unlisted_activity} = CommonAPI.post(user, %{status: "yeah", visibility: "unlisted"})
1193
1194 {:ok, listed_activity} = CommonAPI.post(user, %{status: "yeah"})
1195
1196 [activity] = ActivityPub.fetch_public_activities()
1197
1198 assert activity == listed_activity
1199 end
1200
1201 test "retrieves public activities" do
1202 _activities = ActivityPub.fetch_public_activities()
1203
1204 %{public: public} = ActivityBuilder.public_and_non_public()
1205
1206 activities = ActivityPub.fetch_public_activities()
1207 assert length(activities) == 1
1208 assert Enum.at(activities, 0) == public
1209 end
1210
1211 test "retrieves a maximum of 20 activities" do
1212 ActivityBuilder.insert_list(10)
1213 expected_activities = ActivityBuilder.insert_list(20)
1214
1215 activities = ActivityPub.fetch_public_activities()
1216
1217 assert collect_ids(activities) == collect_ids(expected_activities)
1218 assert length(activities) == 20
1219 end
1220
1221 test "retrieves ids starting from a since_id" do
1222 activities = ActivityBuilder.insert_list(30)
1223 expected_activities = ActivityBuilder.insert_list(10)
1224 since_id = List.last(activities).id
1225
1226 activities = ActivityPub.fetch_public_activities(%{since_id: since_id})
1227
1228 assert collect_ids(activities) == collect_ids(expected_activities)
1229 assert length(activities) == 10
1230 end
1231
1232 test "retrieves ids up to max_id" do
1233 ActivityBuilder.insert_list(10)
1234 expected_activities = ActivityBuilder.insert_list(20)
1235
1236 %{id: max_id} =
1237 10
1238 |> ActivityBuilder.insert_list()
1239 |> List.first()
1240
1241 activities = ActivityPub.fetch_public_activities(%{max_id: max_id})
1242
1243 assert length(activities) == 20
1244 assert collect_ids(activities) == collect_ids(expected_activities)
1245 end
1246
1247 test "paginates via offset/limit" do
1248 _first_part_activities = ActivityBuilder.insert_list(10)
1249 second_part_activities = ActivityBuilder.insert_list(10)
1250
1251 later_activities = ActivityBuilder.insert_list(10)
1252
1253 activities = ActivityPub.fetch_public_activities(%{page: "2", page_size: "20"}, :offset)
1254
1255 assert length(activities) == 20
1256
1257 assert collect_ids(activities) ==
1258 collect_ids(second_part_activities) ++ collect_ids(later_activities)
1259 end
1260
1261 test "doesn't return reblogs for users for whom reblogs have been muted" do
1262 activity = insert(:note_activity)
1263 user = insert(:user)
1264 booster = insert(:user)
1265 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
1266
1267 {:ok, activity} = CommonAPI.repeat(activity.id, booster)
1268
1269 activities = ActivityPub.fetch_activities([], %{muting_user: user})
1270
1271 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
1272 end
1273
1274 test "returns reblogs for users for whom reblogs have not been muted" do
1275 activity = insert(:note_activity)
1276 user = insert(:user)
1277 booster = insert(:user)
1278 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
1279 {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
1280
1281 {:ok, activity} = CommonAPI.repeat(activity.id, booster)
1282
1283 activities = ActivityPub.fetch_activities([], %{muting_user: user})
1284
1285 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
1286 end
1287 end
1288
1289 describe "uploading files" do
1290 setup do
1291 test_file = %Plug.Upload{
1292 content_type: "image/jpeg",
1293 path: Path.absname("test/fixtures/image.jpg"),
1294 filename: "an_image.jpg"
1295 }
1296
1297 %{test_file: test_file}
1298 end
1299
1300 test "sets a description if given", %{test_file: file} do
1301 {:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file")
1302 assert object.data["name"] == "a cool file"
1303 end
1304
1305 test "it sets the default description depending on the configuration", %{test_file: file} do
1306 clear_config([Pleroma.Upload, :default_description])
1307
1308 clear_config([Pleroma.Upload, :default_description], nil)
1309 {:ok, %Object{} = object} = ActivityPub.upload(file)
1310 assert object.data["name"] == ""
1311
1312 clear_config([Pleroma.Upload, :default_description], :filename)
1313 {:ok, %Object{} = object} = ActivityPub.upload(file)
1314 assert object.data["name"] == "an_image.jpg"
1315
1316 clear_config([Pleroma.Upload, :default_description], "unnamed attachment")
1317 {:ok, %Object{} = object} = ActivityPub.upload(file)
1318 assert object.data["name"] == "unnamed attachment"
1319 end
1320
1321 test "copies the file to the configured folder", %{test_file: file} do
1322 clear_config([Pleroma.Upload, :default_description], :filename)
1323 {:ok, %Object{} = object} = ActivityPub.upload(file)
1324 assert object.data["name"] == "an_image.jpg"
1325 end
1326
1327 test "works with base64 encoded images" do
1328 file = %{
1329 img: data_uri()
1330 }
1331
1332 {:ok, %Object{}} = ActivityPub.upload(file)
1333 end
1334 end
1335
1336 describe "fetch the latest Follow" do
1337 test "fetches the latest Follow activity" do
1338 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1339 follower = Repo.get_by(User, ap_id: activity.data["actor"])
1340 followed = Repo.get_by(User, ap_id: activity.data["object"])
1341
1342 assert activity == Utils.fetch_latest_follow(follower, followed)
1343 end
1344 end
1345
1346 describe "unfollowing" do
1347 test "it reverts unfollow activity" do
1348 follower = insert(:user)
1349 followed = insert(:user)
1350
1351 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1352
1353 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1354 assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1355 end
1356
1357 activity = Activity.get_by_id(follow_activity.id)
1358 assert activity.data["type"] == "Follow"
1359 assert activity.data["actor"] == follower.ap_id
1360
1361 assert activity.data["object"] == followed.ap_id
1362 end
1363
1364 test "creates an undo activity for the last follow" do
1365 follower = insert(:user)
1366 followed = insert(:user)
1367
1368 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1369 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1370
1371 assert activity.data["type"] == "Undo"
1372 assert activity.data["actor"] == follower.ap_id
1373
1374 embedded_object = activity.data["object"]
1375 assert is_map(embedded_object)
1376 assert embedded_object["type"] == "Follow"
1377 assert embedded_object["object"] == followed.ap_id
1378 assert embedded_object["id"] == follow_activity.data["id"]
1379 end
1380
1381 test "creates an undo activity for a pending follow request" do
1382 follower = insert(:user)
1383 followed = insert(:user, %{is_locked: true})
1384
1385 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1386 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1387
1388 assert activity.data["type"] == "Undo"
1389 assert activity.data["actor"] == follower.ap_id
1390
1391 embedded_object = activity.data["object"]
1392 assert is_map(embedded_object)
1393 assert embedded_object["type"] == "Follow"
1394 assert embedded_object["object"] == followed.ap_id
1395 assert embedded_object["id"] == follow_activity.data["id"]
1396 end
1397
1398 test "it removes the follow activity if it was local" do
1399 follower = insert(:user, local: true)
1400 followed = insert(:user)
1401
1402 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1403 {:ok, activity} = ActivityPub.unfollow(follower, followed, nil, true)
1404
1405 assert activity.data["type"] == "Undo"
1406 assert activity.data["actor"] == follower.ap_id
1407
1408 follow_activity = Activity.get_by_id(follow_activity.id)
1409 assert is_nil(follow_activity)
1410 assert is_nil(Utils.fetch_latest_follow(follower, followed))
1411
1412 # We need to keep our own undo
1413 undo_activity = Activity.get_by_ap_id(activity.data["id"])
1414 refute is_nil(undo_activity)
1415 end
1416
1417 test "it removes the follow activity if it was remote" do
1418 follower = insert(:user, local: false)
1419 followed = insert(:user)
1420
1421 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1422 {:ok, activity} = ActivityPub.unfollow(follower, followed, nil, false)
1423
1424 assert activity.data["type"] == "Undo"
1425 assert activity.data["actor"] == follower.ap_id
1426
1427 follow_activity = Activity.get_by_id(follow_activity.id)
1428 assert is_nil(follow_activity)
1429 assert is_nil(Utils.fetch_latest_follow(follower, followed))
1430
1431 undo_activity = Activity.get_by_ap_id(activity.data["id"])
1432 assert is_nil(undo_activity)
1433 end
1434 end
1435
1436 describe "timeline post-processing" do
1437 test "it filters broken threads" do
1438 user1 = insert(:user)
1439 user2 = insert(:user)
1440 user3 = insert(:user)
1441
1442 {:ok, user1, user3} = User.follow(user1, user3)
1443 assert User.following?(user1, user3)
1444
1445 {:ok, user2, user3} = User.follow(user2, user3)
1446 assert User.following?(user2, user3)
1447
1448 {:ok, user3, user2} = User.follow(user3, user2)
1449 assert User.following?(user3, user2)
1450
1451 {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"})
1452
1453 {:ok, private_activity_1} = CommonAPI.post(user3, %{status: "hi 2", visibility: "private"})
1454
1455 {:ok, private_activity_2} =
1456 CommonAPI.post(user2, %{
1457 status: "hi 3",
1458 visibility: "private",
1459 in_reply_to_status_id: private_activity_1.id
1460 })
1461
1462 {:ok, private_activity_3} =
1463 CommonAPI.post(user3, %{
1464 status: "hi 4",
1465 visibility: "private",
1466 in_reply_to_status_id: private_activity_2.id
1467 })
1468
1469 activities =
1470 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1471 |> Enum.map(fn a -> a.id end)
1472
1473 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1474
1475 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1476
1477 assert length(activities) == 3
1478
1479 activities =
1480 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{user: user1})
1481 |> Enum.map(fn a -> a.id end)
1482
1483 assert [public_activity.id, private_activity_1.id] == activities
1484 assert length(activities) == 2
1485 end
1486 end
1487
1488 describe "flag/1" do
1489 setup do
1490 reporter = insert(:user)
1491 target_account = insert(:user)
1492 content = "foobar"
1493 {:ok, activity} = CommonAPI.post(target_account, %{status: content})
1494 context = Utils.generate_context_id()
1495
1496 reporter_ap_id = reporter.ap_id
1497 target_ap_id = target_account.ap_id
1498 activity_ap_id = activity.data["id"]
1499
1500 activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1501
1502 {:ok,
1503 %{
1504 reporter: reporter,
1505 context: context,
1506 target_account: target_account,
1507 reported_activity: activity,
1508 content: content,
1509 activity_ap_id: activity_ap_id,
1510 activity_with_object: activity_with_object,
1511 reporter_ap_id: reporter_ap_id,
1512 target_ap_id: target_ap_id
1513 }}
1514 end
1515
1516 test "it can create a Flag activity",
1517 %{
1518 reporter: reporter,
1519 context: context,
1520 target_account: target_account,
1521 reported_activity: reported_activity,
1522 content: content,
1523 activity_ap_id: activity_ap_id,
1524 activity_with_object: activity_with_object,
1525 reporter_ap_id: reporter_ap_id,
1526 target_ap_id: target_ap_id
1527 } do
1528 assert {:ok, activity} =
1529 ActivityPub.flag(%{
1530 actor: reporter,
1531 context: context,
1532 account: target_account,
1533 statuses: [reported_activity],
1534 content: content
1535 })
1536
1537 note_obj = %{
1538 "type" => "Note",
1539 "id" => activity_ap_id,
1540 "content" => content,
1541 "published" => activity_with_object.object.data["published"],
1542 "actor" =>
1543 AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
1544 }
1545
1546 assert %Activity{
1547 actor: ^reporter_ap_id,
1548 data: %{
1549 "type" => "Flag",
1550 "content" => ^content,
1551 "context" => ^context,
1552 "object" => [^target_ap_id, ^note_obj]
1553 }
1554 } = activity
1555 end
1556
1557 test_with_mock "strips status data from Flag, before federating it",
1558 %{
1559 reporter: reporter,
1560 context: context,
1561 target_account: target_account,
1562 reported_activity: reported_activity,
1563 content: content
1564 },
1565 Utils,
1566 [:passthrough],
1567 [] do
1568 {:ok, activity} =
1569 ActivityPub.flag(%{
1570 actor: reporter,
1571 context: context,
1572 account: target_account,
1573 statuses: [reported_activity],
1574 content: content
1575 })
1576
1577 new_data =
1578 put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]])
1579
1580 assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1581 end
1582
1583 test_with_mock "reverts on error",
1584 %{
1585 reporter: reporter,
1586 context: context,
1587 target_account: target_account,
1588 reported_activity: reported_activity,
1589 content: content
1590 },
1591 Utils,
1592 [:passthrough],
1593 maybe_federate: fn _ -> {:error, :reverted} end do
1594 assert {:error, :reverted} =
1595 ActivityPub.flag(%{
1596 actor: reporter,
1597 context: context,
1598 account: target_account,
1599 statuses: [reported_activity],
1600 content: content
1601 })
1602
1603 assert Repo.aggregate(Activity, :count, :id) == 1
1604 assert Repo.aggregate(Object, :count, :id) == 1
1605 assert Repo.aggregate(Notification, :count, :id) == 0
1606 end
1607 end
1608
1609 test "fetch_activities/2 returns activities addressed to a list " do
1610 user = insert(:user)
1611 member = insert(:user)
1612 {:ok, list} = Pleroma.List.create("foo", user)
1613 {:ok, list} = Pleroma.List.follow(list, member)
1614
1615 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1616
1617 activity = Repo.preload(activity, :bookmark)
1618 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1619
1620 assert ActivityPub.fetch_activities([], %{user: user}) == [activity]
1621 end
1622
1623 def data_uri do
1624 File.read!("test/fixtures/avatar_data_uri")
1625 end
1626
1627 describe "fetch_activities_bounded" do
1628 test "fetches private posts for followed users" do
1629 user = insert(:user)
1630
1631 {:ok, activity} =
1632 CommonAPI.post(user, %{
1633 status: "thought I looked cute might delete later :3",
1634 visibility: "private"
1635 })
1636
1637 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1638 assert result.id == activity.id
1639 end
1640
1641 test "fetches only public posts for other users" do
1642 user = insert(:user)
1643 {:ok, activity} = CommonAPI.post(user, %{status: "#cofe", visibility: "public"})
1644
1645 {:ok, _private_activity} =
1646 CommonAPI.post(user, %{
1647 status: "why is tenshi eating a corndog so cute?",
1648 visibility: "private"
1649 })
1650
1651 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1652 assert result.id == activity.id
1653 end
1654 end
1655
1656 describe "fetch_follow_information_for_user" do
1657 test "synchronizes following/followers counters" do
1658 user =
1659 insert(:user,
1660 local: false,
1661 follower_address: "http://localhost:4001/users/fuser2/followers",
1662 following_address: "http://localhost:4001/users/fuser2/following"
1663 )
1664
1665 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1666 assert info.follower_count == 527
1667 assert info.following_count == 267
1668 end
1669
1670 test "detects hidden followers" do
1671 mock(fn env ->
1672 case env.url do
1673 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1674 %Tesla.Env{status: 403, body: ""}
1675
1676 _ ->
1677 apply(HttpRequestMock, :request, [env])
1678 end
1679 end)
1680
1681 user =
1682 insert(:user,
1683 local: false,
1684 follower_address: "http://localhost:4001/users/masto_closed/followers",
1685 following_address: "http://localhost:4001/users/masto_closed/following"
1686 )
1687
1688 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1689 assert follow_info.hide_followers == true
1690 assert follow_info.hide_follows == false
1691 end
1692
1693 test "detects hidden follows" do
1694 mock(fn env ->
1695 case env.url do
1696 "http://localhost:4001/users/masto_closed/following?page=1" ->
1697 %Tesla.Env{status: 403, body: ""}
1698
1699 _ ->
1700 apply(HttpRequestMock, :request, [env])
1701 end
1702 end)
1703
1704 user =
1705 insert(:user,
1706 local: false,
1707 follower_address: "http://localhost:4001/users/masto_closed/followers",
1708 following_address: "http://localhost:4001/users/masto_closed/following"
1709 )
1710
1711 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1712 assert follow_info.hide_followers == false
1713 assert follow_info.hide_follows == true
1714 end
1715
1716 test "detects hidden follows/followers for friendica" do
1717 user =
1718 insert(:user,
1719 local: false,
1720 follower_address: "http://localhost:8080/followers/fuser3",
1721 following_address: "http://localhost:8080/following/fuser3"
1722 )
1723
1724 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1725 assert follow_info.hide_followers == true
1726 assert follow_info.follower_count == 296
1727 assert follow_info.following_count == 32
1728 assert follow_info.hide_follows == true
1729 end
1730
1731 test "doesn't crash when follower and following counters are hidden" do
1732 mock(fn env ->
1733 case env.url do
1734 "http://localhost:4001/users/masto_hidden_counters/following" ->
1735 json(
1736 %{
1737 "@context" => "https://www.w3.org/ns/activitystreams",
1738 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1739 },
1740 headers: HttpRequestMock.activitypub_object_headers()
1741 )
1742
1743 "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1744 %Tesla.Env{status: 403, body: ""}
1745
1746 "http://localhost:4001/users/masto_hidden_counters/followers" ->
1747 json(
1748 %{
1749 "@context" => "https://www.w3.org/ns/activitystreams",
1750 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1751 },
1752 headers: HttpRequestMock.activitypub_object_headers()
1753 )
1754
1755 "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1756 %Tesla.Env{status: 403, body: ""}
1757 end
1758 end)
1759
1760 user =
1761 insert(:user,
1762 local: false,
1763 follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1764 following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1765 )
1766
1767 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1768
1769 assert follow_info.hide_followers == true
1770 assert follow_info.follower_count == 0
1771 assert follow_info.hide_follows == true
1772 assert follow_info.following_count == 0
1773 end
1774 end
1775
1776 describe "fetch_favourites/3" do
1777 test "returns a favourite activities sorted by adds to favorite" do
1778 user = insert(:user)
1779 other_user = insert(:user)
1780 user1 = insert(:user)
1781 user2 = insert(:user)
1782 {:ok, a1} = CommonAPI.post(user1, %{status: "bla"})
1783 {:ok, _a2} = CommonAPI.post(user2, %{status: "traps are happy"})
1784 {:ok, a3} = CommonAPI.post(user2, %{status: "Trees Are "})
1785 {:ok, a4} = CommonAPI.post(user2, %{status: "Agent Smith "})
1786 {:ok, a5} = CommonAPI.post(user1, %{status: "Red or Blue "})
1787
1788 {:ok, _} = CommonAPI.favorite(user, a4.id)
1789 {:ok, _} = CommonAPI.favorite(other_user, a3.id)
1790 {:ok, _} = CommonAPI.favorite(user, a3.id)
1791 {:ok, _} = CommonAPI.favorite(other_user, a5.id)
1792 {:ok, _} = CommonAPI.favorite(user, a5.id)
1793 {:ok, _} = CommonAPI.favorite(other_user, a4.id)
1794 {:ok, _} = CommonAPI.favorite(user, a1.id)
1795 {:ok, _} = CommonAPI.favorite(other_user, a1.id)
1796 result = ActivityPub.fetch_favourites(user)
1797
1798 assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1799
1800 result = ActivityPub.fetch_favourites(user, %{limit: 2})
1801 assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1802 end
1803 end
1804
1805 describe "Move activity" do
1806 test "create" do
1807 %{ap_id: old_ap_id} = old_user = insert(:user)
1808 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1809 follower = insert(:user)
1810 follower_move_opted_out = insert(:user, allow_following_move: false)
1811
1812 User.follow(follower, old_user)
1813 User.follow(follower_move_opted_out, old_user)
1814
1815 assert User.following?(follower, old_user)
1816 assert User.following?(follower_move_opted_out, old_user)
1817
1818 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1819
1820 assert %Activity{
1821 actor: ^old_ap_id,
1822 data: %{
1823 "actor" => ^old_ap_id,
1824 "object" => ^old_ap_id,
1825 "target" => ^new_ap_id,
1826 "type" => "Move"
1827 },
1828 local: true,
1829 recipients: recipients
1830 } = activity
1831
1832 assert old_user.follower_address in recipients
1833
1834 params = %{
1835 "op" => "move_following",
1836 "origin_id" => old_user.id,
1837 "target_id" => new_user.id
1838 }
1839
1840 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1841
1842 Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
1843
1844 refute User.following?(follower, old_user)
1845 assert User.following?(follower, new_user)
1846
1847 assert User.following?(follower_move_opted_out, old_user)
1848 refute User.following?(follower_move_opted_out, new_user)
1849
1850 activity = %Activity{activity | object: nil}
1851
1852 assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1853
1854 assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1855 end
1856
1857 test "old user must be in the new user's `also_known_as` list" do
1858 old_user = insert(:user)
1859 new_user = insert(:user)
1860
1861 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1862 ActivityPub.move(old_user, new_user)
1863 end
1864
1865 test "do not move remote user following relationships" do
1866 %{ap_id: old_ap_id} = old_user = insert(:user)
1867 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1868 follower_remote = insert(:user, local: false)
1869
1870 User.follow(follower_remote, old_user)
1871
1872 assert User.following?(follower_remote, old_user)
1873
1874 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1875
1876 assert %Activity{
1877 actor: ^old_ap_id,
1878 data: %{
1879 "actor" => ^old_ap_id,
1880 "object" => ^old_ap_id,
1881 "target" => ^new_ap_id,
1882 "type" => "Move"
1883 },
1884 local: true
1885 } = activity
1886
1887 params = %{
1888 "op" => "move_following",
1889 "origin_id" => old_user.id,
1890 "target_id" => new_user.id
1891 }
1892
1893 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1894
1895 Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
1896
1897 assert User.following?(follower_remote, old_user)
1898 refute User.following?(follower_remote, new_user)
1899 end
1900 end
1901
1902 test "doesn't retrieve replies activities with exclude_replies" do
1903 user = insert(:user)
1904
1905 {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
1906
1907 {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id})
1908
1909 [result] = ActivityPub.fetch_public_activities(%{exclude_replies: true})
1910
1911 assert result.id == activity.id
1912
1913 assert length(ActivityPub.fetch_public_activities()) == 2
1914 end
1915
1916 describe "replies filtering with public messages" do
1917 setup :public_messages
1918
1919 test "public timeline", %{users: %{u1: user}} do
1920 activities_ids =
1921 %{}
1922 |> Map.put(:type, ["Create", "Announce"])
1923 |> Map.put(:local_only, false)
1924 |> Map.put(:blocking_user, user)
1925 |> Map.put(:muting_user, user)
1926 |> Map.put(:reply_filtering_user, user)
1927 |> ActivityPub.fetch_public_activities()
1928 |> Enum.map(& &1.id)
1929
1930 assert length(activities_ids) == 16
1931 end
1932
1933 test "public timeline with reply_visibility `following`", %{
1934 users: %{u1: user},
1935 u1: u1,
1936 u2: u2,
1937 u3: u3,
1938 u4: u4,
1939 activities: activities
1940 } do
1941 activities_ids =
1942 %{}
1943 |> Map.put(:type, ["Create", "Announce"])
1944 |> Map.put(:local_only, false)
1945 |> Map.put(:blocking_user, user)
1946 |> Map.put(:muting_user, user)
1947 |> Map.put(:reply_visibility, "following")
1948 |> Map.put(:reply_filtering_user, user)
1949 |> ActivityPub.fetch_public_activities()
1950 |> Enum.map(& &1.id)
1951
1952 assert length(activities_ids) == 14
1953
1954 visible_ids =
1955 Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
1956
1957 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1958 end
1959
1960 test "public timeline with reply_visibility `self`", %{
1961 users: %{u1: user},
1962 u1: u1,
1963 u2: u2,
1964 u3: u3,
1965 u4: u4,
1966 activities: activities
1967 } do
1968 activities_ids =
1969 %{}
1970 |> Map.put(:type, ["Create", "Announce"])
1971 |> Map.put(:local_only, false)
1972 |> Map.put(:blocking_user, user)
1973 |> Map.put(:muting_user, user)
1974 |> Map.put(:reply_visibility, "self")
1975 |> Map.put(:reply_filtering_user, user)
1976 |> ActivityPub.fetch_public_activities()
1977 |> Enum.map(& &1.id)
1978
1979 assert length(activities_ids) == 10
1980 visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
1981 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1982 end
1983
1984 test "home timeline", %{
1985 users: %{u1: user},
1986 activities: activities,
1987 u1: u1,
1988 u2: u2,
1989 u3: u3,
1990 u4: u4
1991 } do
1992 params =
1993 %{}
1994 |> Map.put(:type, ["Create", "Announce"])
1995 |> Map.put(:blocking_user, user)
1996 |> Map.put(:muting_user, user)
1997 |> Map.put(:user, user)
1998 |> Map.put(:reply_filtering_user, user)
1999
2000 activities_ids =
2001 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2002 |> Enum.map(& &1.id)
2003
2004 assert length(activities_ids) == 13
2005
2006 visible_ids =
2007 Map.values(u1) ++
2008 Map.values(u3) ++
2009 [
2010 activities[:a1],
2011 activities[:a2],
2012 activities[:a4],
2013 u2[:r1],
2014 u2[:r3],
2015 u4[:r1],
2016 u4[:r2]
2017 ]
2018
2019 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2020 end
2021
2022 test "home timeline with reply_visibility `following`", %{
2023 users: %{u1: user},
2024 activities: activities,
2025 u1: u1,
2026 u2: u2,
2027 u3: u3,
2028 u4: u4
2029 } do
2030 params =
2031 %{}
2032 |> Map.put(:type, ["Create", "Announce"])
2033 |> Map.put(:blocking_user, user)
2034 |> Map.put(:muting_user, user)
2035 |> Map.put(:user, user)
2036 |> Map.put(:reply_visibility, "following")
2037 |> Map.put(:reply_filtering_user, user)
2038
2039 activities_ids =
2040 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2041 |> Enum.map(& &1.id)
2042
2043 assert length(activities_ids) == 11
2044
2045 visible_ids =
2046 Map.values(u1) ++
2047 [
2048 activities[:a1],
2049 activities[:a2],
2050 activities[:a4],
2051 u2[:r1],
2052 u2[:r3],
2053 u3[:r1],
2054 u4[:r1],
2055 u4[:r2]
2056 ]
2057
2058 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2059 end
2060
2061 test "home timeline with reply_visibility `self`", %{
2062 users: %{u1: user},
2063 activities: activities,
2064 u1: u1,
2065 u2: u2,
2066 u3: u3,
2067 u4: u4
2068 } do
2069 params =
2070 %{}
2071 |> Map.put(:type, ["Create", "Announce"])
2072 |> Map.put(:blocking_user, user)
2073 |> Map.put(:muting_user, user)
2074 |> Map.put(:user, user)
2075 |> Map.put(:reply_visibility, "self")
2076 |> Map.put(:reply_filtering_user, user)
2077
2078 activities_ids =
2079 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2080 |> Enum.map(& &1.id)
2081
2082 assert length(activities_ids) == 9
2083
2084 visible_ids =
2085 Map.values(u1) ++
2086 [
2087 activities[:a1],
2088 activities[:a2],
2089 activities[:a4],
2090 u2[:r1],
2091 u3[:r1],
2092 u4[:r1]
2093 ]
2094
2095 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2096 end
2097
2098 test "filtering out announces where the user is the actor of the announced message" do
2099 user = insert(:user)
2100 other_user = insert(:user)
2101 third_user = insert(:user)
2102 User.follow(user, other_user)
2103
2104 {:ok, post} = CommonAPI.post(user, %{status: "yo"})
2105 {:ok, other_post} = CommonAPI.post(third_user, %{status: "yo"})
2106 {:ok, _announce} = CommonAPI.repeat(post.id, other_user)
2107 {:ok, _announce} = CommonAPI.repeat(post.id, third_user)
2108 {:ok, announce} = CommonAPI.repeat(other_post.id, other_user)
2109
2110 params = %{
2111 type: ["Announce"]
2112 }
2113
2114 results =
2115 [user.ap_id | User.following(user)]
2116 |> ActivityPub.fetch_activities(params)
2117
2118 assert length(results) == 3
2119
2120 params = %{
2121 type: ["Announce"],
2122 announce_filtering_user: user
2123 }
2124
2125 [result] =
2126 [user.ap_id | User.following(user)]
2127 |> ActivityPub.fetch_activities(params)
2128
2129 assert result.id == announce.id
2130 end
2131 end
2132
2133 describe "replies filtering with private messages" do
2134 setup :private_messages
2135
2136 test "public timeline", %{users: %{u1: user}} do
2137 activities_ids =
2138 %{}
2139 |> Map.put(:type, ["Create", "Announce"])
2140 |> Map.put(:local_only, false)
2141 |> Map.put(:blocking_user, user)
2142 |> Map.put(:muting_user, user)
2143 |> Map.put(:user, user)
2144 |> ActivityPub.fetch_public_activities()
2145 |> Enum.map(& &1.id)
2146
2147 assert activities_ids == []
2148 end
2149
2150 test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2151 activities_ids =
2152 %{}
2153 |> Map.put(:type, ["Create", "Announce"])
2154 |> Map.put(:local_only, false)
2155 |> Map.put(:blocking_user, user)
2156 |> Map.put(:muting_user, user)
2157 |> Map.put(:reply_visibility, "following")
2158 |> Map.put(:reply_filtering_user, user)
2159 |> Map.put(:user, user)
2160 |> ActivityPub.fetch_public_activities()
2161 |> Enum.map(& &1.id)
2162
2163 assert activities_ids == []
2164 end
2165
2166 test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
2167 activities_ids =
2168 %{}
2169 |> Map.put(:type, ["Create", "Announce"])
2170 |> Map.put(:local_only, false)
2171 |> Map.put(:blocking_user, user)
2172 |> Map.put(:muting_user, user)
2173 |> Map.put(:reply_visibility, "self")
2174 |> Map.put(:reply_filtering_user, user)
2175 |> Map.put(:user, user)
2176 |> ActivityPub.fetch_public_activities()
2177 |> Enum.map(& &1.id)
2178
2179 assert activities_ids == []
2180
2181 activities_ids =
2182 %{}
2183 |> Map.put(:reply_visibility, "self")
2184 |> Map.put(:reply_filtering_user, nil)
2185 |> ActivityPub.fetch_public_activities()
2186
2187 assert activities_ids == []
2188 end
2189
2190 test "home timeline", %{users: %{u1: user}} do
2191 params =
2192 %{}
2193 |> Map.put(:type, ["Create", "Announce"])
2194 |> Map.put(:blocking_user, user)
2195 |> Map.put(:muting_user, user)
2196 |> Map.put(: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) == 12
2203 end
2204
2205 test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2206 params =
2207 %{}
2208 |> Map.put(:type, ["Create", "Announce"])
2209 |> Map.put(:blocking_user, user)
2210 |> Map.put(:muting_user, user)
2211 |> Map.put(:user, user)
2212 |> Map.put(:reply_visibility, "following")
2213 |> Map.put(:reply_filtering_user, user)
2214
2215 activities_ids =
2216 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2217 |> Enum.map(& &1.id)
2218
2219 assert length(activities_ids) == 12
2220 end
2221
2222 test "home timeline with default reply_visibility `self`", %{
2223 users: %{u1: user},
2224 activities: activities,
2225 u1: u1,
2226 u2: u2,
2227 u3: u3,
2228 u4: u4
2229 } do
2230 params =
2231 %{}
2232 |> Map.put(:type, ["Create", "Announce"])
2233 |> Map.put(:blocking_user, user)
2234 |> Map.put(:muting_user, user)
2235 |> Map.put(:user, user)
2236 |> Map.put(:reply_visibility, "self")
2237 |> Map.put(:reply_filtering_user, user)
2238
2239 activities_ids =
2240 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2241 |> Enum.map(& &1.id)
2242
2243 assert length(activities_ids) == 10
2244
2245 visible_ids =
2246 Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
2247
2248 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2249 end
2250 end
2251
2252 defp public_messages(_) do
2253 [u1, u2, u3, u4] = insert_list(4, :user)
2254 {:ok, u1, u2} = User.follow(u1, u2)
2255 {:ok, u2, u1} = User.follow(u2, u1)
2256 {:ok, u1, u4} = User.follow(u1, u4)
2257 {:ok, u4, u1} = User.follow(u4, u1)
2258
2259 {:ok, u2, u3} = User.follow(u2, u3)
2260 {:ok, u3, u2} = User.follow(u3, u2)
2261
2262 {:ok, a1} = CommonAPI.post(u1, %{status: "Status"})
2263
2264 {:ok, r1_1} =
2265 CommonAPI.post(u2, %{
2266 status: "@#{u1.nickname} reply from u2 to u1",
2267 in_reply_to_status_id: a1.id
2268 })
2269
2270 {:ok, r1_2} =
2271 CommonAPI.post(u3, %{
2272 status: "@#{u1.nickname} reply from u3 to u1",
2273 in_reply_to_status_id: a1.id
2274 })
2275
2276 {:ok, r1_3} =
2277 CommonAPI.post(u4, %{
2278 status: "@#{u1.nickname} reply from u4 to u1",
2279 in_reply_to_status_id: a1.id
2280 })
2281
2282 {:ok, a2} = CommonAPI.post(u2, %{status: "Status"})
2283
2284 {:ok, r2_1} =
2285 CommonAPI.post(u1, %{
2286 status: "@#{u2.nickname} reply from u1 to u2",
2287 in_reply_to_status_id: a2.id
2288 })
2289
2290 {:ok, r2_2} =
2291 CommonAPI.post(u3, %{
2292 status: "@#{u2.nickname} reply from u3 to u2",
2293 in_reply_to_status_id: a2.id
2294 })
2295
2296 {:ok, r2_3} =
2297 CommonAPI.post(u4, %{
2298 status: "@#{u2.nickname} reply from u4 to u2",
2299 in_reply_to_status_id: a2.id
2300 })
2301
2302 {:ok, a3} = CommonAPI.post(u3, %{status: "Status"})
2303
2304 {:ok, r3_1} =
2305 CommonAPI.post(u1, %{
2306 status: "@#{u3.nickname} reply from u1 to u3",
2307 in_reply_to_status_id: a3.id
2308 })
2309
2310 {:ok, r3_2} =
2311 CommonAPI.post(u2, %{
2312 status: "@#{u3.nickname} reply from u2 to u3",
2313 in_reply_to_status_id: a3.id
2314 })
2315
2316 {:ok, r3_3} =
2317 CommonAPI.post(u4, %{
2318 status: "@#{u3.nickname} reply from u4 to u3",
2319 in_reply_to_status_id: a3.id
2320 })
2321
2322 {:ok, a4} = CommonAPI.post(u4, %{status: "Status"})
2323
2324 {:ok, r4_1} =
2325 CommonAPI.post(u1, %{
2326 status: "@#{u4.nickname} reply from u1 to u4",
2327 in_reply_to_status_id: a4.id
2328 })
2329
2330 {:ok, r4_2} =
2331 CommonAPI.post(u2, %{
2332 status: "@#{u4.nickname} reply from u2 to u4",
2333 in_reply_to_status_id: a4.id
2334 })
2335
2336 {:ok, r4_3} =
2337 CommonAPI.post(u3, %{
2338 status: "@#{u4.nickname} reply from u3 to u4",
2339 in_reply_to_status_id: a4.id
2340 })
2341
2342 {:ok,
2343 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2344 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2345 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2346 u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
2347 u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
2348 u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
2349 end
2350
2351 defp private_messages(_) do
2352 [u1, u2, u3, u4] = insert_list(4, :user)
2353 {:ok, u1, u2} = User.follow(u1, u2)
2354 {:ok, u2, u1} = User.follow(u2, u1)
2355 {:ok, u1, u3} = User.follow(u1, u3)
2356 {:ok, u3, u1} = User.follow(u3, u1)
2357 {:ok, u1, u4} = User.follow(u1, u4)
2358 {:ok, u4, u1} = User.follow(u4, u1)
2359
2360 {:ok, u2, u3} = User.follow(u2, u3)
2361 {:ok, u3, u2} = User.follow(u3, u2)
2362
2363 {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"})
2364
2365 {:ok, r1_1} =
2366 CommonAPI.post(u2, %{
2367 status: "@#{u1.nickname} reply from u2 to u1",
2368 in_reply_to_status_id: a1.id,
2369 visibility: "private"
2370 })
2371
2372 {:ok, r1_2} =
2373 CommonAPI.post(u3, %{
2374 status: "@#{u1.nickname} reply from u3 to u1",
2375 in_reply_to_status_id: a1.id,
2376 visibility: "private"
2377 })
2378
2379 {:ok, r1_3} =
2380 CommonAPI.post(u4, %{
2381 status: "@#{u1.nickname} reply from u4 to u1",
2382 in_reply_to_status_id: a1.id,
2383 visibility: "private"
2384 })
2385
2386 {:ok, a2} = CommonAPI.post(u2, %{status: "Status", visibility: "private"})
2387
2388 {:ok, r2_1} =
2389 CommonAPI.post(u1, %{
2390 status: "@#{u2.nickname} reply from u1 to u2",
2391 in_reply_to_status_id: a2.id,
2392 visibility: "private"
2393 })
2394
2395 {:ok, r2_2} =
2396 CommonAPI.post(u3, %{
2397 status: "@#{u2.nickname} reply from u3 to u2",
2398 in_reply_to_status_id: a2.id,
2399 visibility: "private"
2400 })
2401
2402 {:ok, a3} = CommonAPI.post(u3, %{status: "Status", visibility: "private"})
2403
2404 {:ok, r3_1} =
2405 CommonAPI.post(u1, %{
2406 status: "@#{u3.nickname} reply from u1 to u3",
2407 in_reply_to_status_id: a3.id,
2408 visibility: "private"
2409 })
2410
2411 {:ok, r3_2} =
2412 CommonAPI.post(u2, %{
2413 status: "@#{u3.nickname} reply from u2 to u3",
2414 in_reply_to_status_id: a3.id,
2415 visibility: "private"
2416 })
2417
2418 {:ok, a4} = CommonAPI.post(u4, %{status: "Status", visibility: "private"})
2419
2420 {:ok, r4_1} =
2421 CommonAPI.post(u1, %{
2422 status: "@#{u4.nickname} reply from u1 to u4",
2423 in_reply_to_status_id: a4.id,
2424 visibility: "private"
2425 })
2426
2427 {:ok,
2428 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2429 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2430 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2431 u2: %{r1: r2_1.id, r2: r2_2.id},
2432 u3: %{r1: r3_1.id, r2: r3_2.id},
2433 u4: %{r1: r4_1.id}}
2434 end
2435
2436 describe "maybe_update_follow_information/1" do
2437 setup do
2438 clear_config([:instance, :external_user_synchronization], true)
2439
2440 user = %{
2441 local: false,
2442 ap_id: "https://gensokyo.2hu/users/raymoo",
2443 following_address: "https://gensokyo.2hu/users/following",
2444 follower_address: "https://gensokyo.2hu/users/followers",
2445 type: "Person"
2446 }
2447
2448 %{user: user}
2449 end
2450
2451 test "logs an error when it can't fetch the info", %{user: user} do
2452 assert capture_log(fn ->
2453 ActivityPub.maybe_update_follow_information(user)
2454 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2455 end
2456
2457 test "just returns the input if the user type is Application", %{
2458 user: user
2459 } do
2460 user =
2461 user
2462 |> Map.put(:type, "Application")
2463
2464 refute capture_log(fn ->
2465 assert ^user = ActivityPub.maybe_update_follow_information(user)
2466 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2467 end
2468
2469 test "it just returns the input if the user has no following/follower addresses", %{
2470 user: user
2471 } do
2472 user =
2473 user
2474 |> Map.put(:following_address, nil)
2475 |> Map.put(:follower_address, nil)
2476
2477 refute capture_log(fn ->
2478 assert ^user = ActivityPub.maybe_update_follow_information(user)
2479 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2480 end
2481 end
2482
2483 describe "global activity expiration" do
2484 test "creates an activity expiration for local Create activities" do
2485 clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy)
2486
2487 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
2488 {:ok, follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
2489
2490 assert_enqueued(
2491 worker: Pleroma.Workers.PurgeExpiredActivity,
2492 args: %{activity_id: activity.id},
2493 scheduled_at:
2494 activity.inserted_at
2495 |> DateTime.from_naive!("Etc/UTC")
2496 |> Timex.shift(days: 365)
2497 )
2498
2499 refute_enqueued(
2500 worker: Pleroma.Workers.PurgeExpiredActivity,
2501 args: %{activity_id: follow.id}
2502 )
2503 end
2504 end
2505
2506 describe "handling of clashing nicknames" do
2507 test "renames an existing user with a clashing nickname and a different ap id" do
2508 orig_user =
2509 insert(
2510 :user,
2511 local: false,
2512 nickname: "admin@mastodon.example.org",
2513 ap_id: "http://mastodon.example.org/users/harinezumigari"
2514 )
2515
2516 %{
2517 nickname: orig_user.nickname,
2518 ap_id: orig_user.ap_id <> "part_2"
2519 }
2520 |> ActivityPub.maybe_handle_clashing_nickname()
2521
2522 user = User.get_by_id(orig_user.id)
2523
2524 assert user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
2525 end
2526
2527 test "does nothing with a clashing nickname and the same ap id" do
2528 orig_user =
2529 insert(
2530 :user,
2531 local: false,
2532 nickname: "admin@mastodon.example.org",
2533 ap_id: "http://mastodon.example.org/users/harinezumigari"
2534 )
2535
2536 %{
2537 nickname: orig_user.nickname,
2538 ap_id: orig_user.ap_id
2539 }
2540 |> ActivityPub.maybe_handle_clashing_nickname()
2541
2542 user = User.get_by_id(orig_user.id)
2543
2544 assert user.nickname == orig_user.nickname
2545 end
2546 end
2547
2548 describe "reply filtering" do
2549 test "`following` still contains announcements by friends" do
2550 user = insert(:user)
2551 followed = insert(:user)
2552 not_followed = insert(:user)
2553
2554 User.follow(user, followed)
2555
2556 {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2557
2558 {:ok, not_followed_to_followed} =
2559 CommonAPI.post(not_followed, %{
2560 status: "Also hello",
2561 in_reply_to_status_id: followed_post.id
2562 })
2563
2564 {:ok, retoot} = CommonAPI.repeat(not_followed_to_followed.id, followed)
2565
2566 params =
2567 %{}
2568 |> Map.put(:type, ["Create", "Announce"])
2569 |> Map.put(:blocking_user, user)
2570 |> Map.put(:muting_user, user)
2571 |> Map.put(:reply_filtering_user, user)
2572 |> Map.put(:reply_visibility, "following")
2573 |> Map.put(:announce_filtering_user, user)
2574 |> Map.put(:user, user)
2575
2576 activities =
2577 [user.ap_id | User.following(user)]
2578 |> ActivityPub.fetch_activities(params)
2579
2580 followed_post_id = followed_post.id
2581 retoot_id = retoot.id
2582
2583 assert [%{id: ^followed_post_id}, %{id: ^retoot_id}] = activities
2584
2585 assert length(activities) == 2
2586 end
2587
2588 # This test is skipped because, while this is the desired behavior,
2589 # there seems to be no good way to achieve it with the method that
2590 # we currently use for detecting to who a reply is directed.
2591 # This is a TODO and should be fixed by a later rewrite of the code
2592 # in question.
2593 @tag skip: true
2594 test "`following` still contains self-replies by friends" do
2595 user = insert(:user)
2596 followed = insert(:user)
2597 not_followed = insert(:user)
2598
2599 User.follow(user, followed)
2600
2601 {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2602 {:ok, not_followed_post} = CommonAPI.post(not_followed, %{status: "Also hello"})
2603
2604 {:ok, _followed_to_not_followed} =
2605 CommonAPI.post(followed, %{status: "sup", in_reply_to_status_id: not_followed_post.id})
2606
2607 {:ok, _followed_self_reply} =
2608 CommonAPI.post(followed, %{status: "Also cofe", in_reply_to_status_id: followed_post.id})
2609
2610 params =
2611 %{}
2612 |> Map.put(:type, ["Create", "Announce"])
2613 |> Map.put(:blocking_user, user)
2614 |> Map.put(:muting_user, user)
2615 |> Map.put(:reply_filtering_user, user)
2616 |> Map.put(:reply_visibility, "following")
2617 |> Map.put(:announce_filtering_user, user)
2618 |> Map.put(:user, user)
2619
2620 activities =
2621 [user.ap_id | User.following(user)]
2622 |> ActivityPub.fetch_activities(params)
2623
2624 assert length(activities) == 2
2625 end
2626 end
2627
2628 test "allow fetching of accounts with an empty string name field" do
2629 Tesla.Mock.mock(fn
2630 %{method: :get, url: "https://princess.cat/users/mewmew"} ->
2631 file = File.read!("test/fixtures/mewmew_no_name.json")
2632 %Tesla.Env{status: 200, body: file, headers: HttpRequestMock.activitypub_object_headers()}
2633 end)
2634
2635 {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew")
2636 assert user.name == " "
2637 end
2638
2639 describe "persist/1" do
2640 test "should not persist remote delete activities" do
2641 poster = insert(:user, local: false)
2642 {:ok, post} = CommonAPI.post(poster, %{status: "hhhhhh"})
2643
2644 {:ok, delete_data, meta} = Builder.delete(poster, post)
2645 local_opts = Keyword.put(meta, :local, false)
2646 {:ok, act, _meta} = ActivityPub.persist(delete_data, local_opts)
2647 refute act.inserted_at
2648 end
2649
2650 test "should not persist remote undo activities" do
2651 poster = insert(:user, local: false)
2652 liker = insert(:user, local: false)
2653 {:ok, post} = CommonAPI.post(poster, %{status: "hhhhhh"})
2654 {:ok, like} = CommonAPI.favorite(liker, post.id)
2655
2656 {:ok, undo_data, meta} = Builder.undo(liker, like)
2657 local_opts = Keyword.put(meta, :local, false)
2658 {:ok, act, _meta} = ActivityPub.persist(undo_data, local_opts)
2659 refute act.inserted_at
2660 end
2661 end
2662 end