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