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