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