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