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