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