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