Merge branch 'delete-account-fix' into 'develop'
[akkoma] / test / pleroma / web / activity_pub / activity_pub_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
6 use Pleroma.DataCase
7 use Oban.Testing, repo: Pleroma.Repo
8
9 alias Pleroma.Activity
10 alias Pleroma.Builders.ActivityBuilder
11 alias Pleroma.Config
12 alias Pleroma.Notification
13 alias Pleroma.Object
14 alias Pleroma.User
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.ActivityPub.Utils
17 alias Pleroma.Web.AdminAPI.AccountView
18 alias Pleroma.Web.CommonAPI
19
20 import ExUnit.CaptureLog
21 import Mock
22 import Pleroma.Factory
23 import Tesla.Mock
24
25 setup do
26 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
27 :ok
28 end
29
30 setup do: clear_config([:instance, :federating])
31
32 describe "streaming out participations" do
33 test "it streams them out" do
34 user = insert(:user)
35 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
36
37 {:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity)
38
39 participations =
40 conversation.participations
41 |> Repo.preload(:user)
42
43 with_mock Pleroma.Web.Streamer,
44 stream: fn _, _ -> nil end do
45 ActivityPub.stream_out_participations(conversation.participations)
46
47 assert called(Pleroma.Web.Streamer.stream("participation", participations))
48 end
49 end
50
51 test "streams them out on activity creation" do
52 user_one = insert(:user)
53 user_two = insert(:user)
54
55 with_mock Pleroma.Web.Streamer,
56 stream: fn _, _ -> nil end do
57 {:ok, activity} =
58 CommonAPI.post(user_one, %{
59 status: "@#{user_two.nickname}",
60 visibility: "direct"
61 })
62
63 conversation =
64 activity.data["context"]
65 |> Pleroma.Conversation.get_for_ap_id()
66 |> Repo.preload(participations: :user)
67
68 assert called(Pleroma.Web.Streamer.stream("participation", conversation.participations))
69 end
70 end
71 end
72
73 describe "fetching restricted by visibility" do
74 test "it restricts by the appropriate visibility" do
75 user = insert(:user)
76
77 {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
78
79 {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
80
81 {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
82
83 {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
84
85 activities = ActivityPub.fetch_activities([], %{visibility: "direct", actor_id: user.ap_id})
86
87 assert activities == [direct_activity]
88
89 activities =
90 ActivityPub.fetch_activities([], %{visibility: "unlisted", actor_id: user.ap_id})
91
92 assert activities == [unlisted_activity]
93
94 activities =
95 ActivityPub.fetch_activities([], %{visibility: "private", actor_id: user.ap_id})
96
97 assert activities == [private_activity]
98
99 activities = ActivityPub.fetch_activities([], %{visibility: "public", actor_id: user.ap_id})
100
101 assert activities == [public_activity]
102
103 activities =
104 ActivityPub.fetch_activities([], %{
105 visibility: ~w[private public],
106 actor_id: user.ap_id
107 })
108
109 assert activities == [public_activity, private_activity]
110 end
111 end
112
113 describe "fetching excluded by visibility" do
114 test "it excludes by the appropriate visibility" do
115 user = insert(:user)
116
117 {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
118
119 {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
120
121 {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
122
123 {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
124
125 activities =
126 ActivityPub.fetch_activities([], %{
127 exclude_visibilities: "direct",
128 actor_id: user.ap_id
129 })
130
131 assert public_activity in activities
132 assert unlisted_activity in activities
133 assert private_activity in activities
134 refute direct_activity in activities
135
136 activities =
137 ActivityPub.fetch_activities([], %{
138 exclude_visibilities: "unlisted",
139 actor_id: user.ap_id
140 })
141
142 assert public_activity in activities
143 refute unlisted_activity in activities
144 assert private_activity in activities
145 assert direct_activity in activities
146
147 activities =
148 ActivityPub.fetch_activities([], %{
149 exclude_visibilities: "private",
150 actor_id: user.ap_id
151 })
152
153 assert public_activity in activities
154 assert unlisted_activity in activities
155 refute private_activity in activities
156 assert direct_activity in activities
157
158 activities =
159 ActivityPub.fetch_activities([], %{
160 exclude_visibilities: "public",
161 actor_id: user.ap_id
162 })
163
164 refute public_activity in activities
165 assert unlisted_activity in activities
166 assert private_activity in activities
167 assert direct_activity in activities
168 end
169 end
170
171 describe "building a user from his ap id" do
172 test "it returns a user" do
173 user_id = "http://mastodon.example.org/users/admin"
174 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
175 assert user.ap_id == user_id
176 assert user.nickname == "admin@mastodon.example.org"
177 assert user.ap_enabled
178 assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
179 end
180
181 test "it returns a user that is invisible" do
182 user_id = "http://mastodon.example.org/users/relay"
183 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
184 assert User.invisible?(user)
185 end
186
187 test "it returns a user that accepts chat messages" do
188 user_id = "http://mastodon.example.org/users/admin"
189 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
190
191 assert user.accepts_chat_messages
192 end
193
194 test "works for guppe actors" do
195 user_id = "https://gup.pe/u/bernie2020"
196
197 Tesla.Mock.mock(fn
198 %{method: :get, url: ^user_id} ->
199 %Tesla.Env{
200 status: 200,
201 body: File.read!("test/fixtures/guppe-actor.json"),
202 headers: [{"content-type", "application/activity+json"}]
203 }
204 end)
205
206 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
207
208 assert user.name == "Bernie2020 group"
209 assert user.actor_type == "Group"
210 end
211
212 test "works for bridgy actors" do
213 user_id = "https://fed.brid.gy/jk.nipponalba.scot"
214
215 Tesla.Mock.mock(fn
216 %{method: :get, url: ^user_id} ->
217 %Tesla.Env{
218 status: 200,
219 body: File.read!("test/fixtures/bridgy/actor.json"),
220 headers: [{"content-type", "application/activity+json"}]
221 }
222 end)
223
224 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
225
226 assert user.actor_type == "Person"
227
228 assert user.avatar == %{
229 "type" => "Image",
230 "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
231 }
232
233 assert user.banner == %{
234 "type" => "Image",
235 "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
236 }
237 end
238
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 test "always see your own posts even when they address people you block" do
794 user = insert(:user)
795 blockee = insert(:user)
796
797 {:ok, _} = User.block(user, blockee)
798 {:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{blockee.nickname}"})
799
800 activities = ActivityPub.fetch_activities([], %{blocking_user: user})
801
802 assert Enum.member?(activities, activity)
803 end
804
805 test "doesn't return transitive interactions concerning blocked users" do
806 blocker = insert(:user)
807 blockee = insert(:user)
808 friend = insert(:user)
809
810 {:ok, _user_relationship} = User.block(blocker, blockee)
811
812 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
813
814 {:ok, activity_two} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
815
816 {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
817
818 {:ok, activity_four} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
819
820 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker})
821
822 assert Enum.member?(activities, activity_one)
823 refute Enum.member?(activities, activity_two)
824 refute Enum.member?(activities, activity_three)
825 refute Enum.member?(activities, activity_four)
826 end
827
828 test "doesn't return announce activities with blocked users in 'to'" do
829 blocker = insert(:user)
830 blockee = insert(:user)
831 friend = insert(:user)
832
833 {:ok, _user_relationship} = User.block(blocker, blockee)
834
835 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
836
837 {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
838
839 {:ok, activity_three} = CommonAPI.repeat(activity_two.id, friend)
840
841 activities =
842 ActivityPub.fetch_activities([], %{blocking_user: blocker})
843 |> Enum.map(fn act -> act.id end)
844
845 assert Enum.member?(activities, activity_one.id)
846 refute Enum.member?(activities, activity_two.id)
847 refute Enum.member?(activities, activity_three.id)
848 end
849
850 test "doesn't return announce activities with blocked users in 'cc'" do
851 blocker = insert(:user)
852 blockee = insert(:user)
853 friend = insert(:user)
854
855 {:ok, _user_relationship} = User.block(blocker, blockee)
856
857 {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
858
859 {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
860
861 assert object = Pleroma.Object.normalize(activity_two, fetch: false)
862
863 data = %{
864 "actor" => friend.ap_id,
865 "object" => object.data["id"],
866 "context" => object.data["context"],
867 "type" => "Announce",
868 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
869 "cc" => [blockee.ap_id]
870 }
871
872 assert {:ok, activity_three} = ActivityPub.insert(data)
873
874 activities =
875 ActivityPub.fetch_activities([], %{blocking_user: blocker})
876 |> Enum.map(fn act -> act.id end)
877
878 assert Enum.member?(activities, activity_one.id)
879 refute Enum.member?(activities, activity_two.id)
880 refute Enum.member?(activities, activity_three.id)
881 end
882
883 test "doesn't return activities from blocked domains" do
884 domain = "dogwhistle.zone"
885 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
886 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
887 activity = insert(:note_activity, %{note: note})
888 user = insert(:user)
889 {:ok, user} = User.block_domain(user, domain)
890
891 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
892
893 refute activity in activities
894
895 followed_user = insert(:user)
896 CommonAPI.follow(user, followed_user)
897 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user)
898
899 activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
900
901 refute repeat_activity in activities
902 end
903
904 test "see your own posts even when they adress actors from blocked domains" do
905 user = insert(:user)
906
907 domain = "dogwhistle.zone"
908 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
909
910 {:ok, user} = User.block_domain(user, domain)
911
912 {:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{domain_user.nickname}"})
913
914 activities = ActivityPub.fetch_activities([], %{blocking_user: user})
915
916 assert Enum.member?(activities, activity)
917 end
918
919 test "does return activities from followed users on blocked domains" do
920 domain = "meanies.social"
921 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
922 blocker = insert(:user)
923
924 {:ok, blocker, domain_user} = User.follow(blocker, domain_user)
925 {:ok, blocker} = User.block_domain(blocker, domain)
926
927 assert User.following?(blocker, domain_user)
928 assert User.blocks_domain?(blocker, domain_user)
929 refute User.blocks?(blocker, domain_user)
930
931 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
932 activity = insert(:note_activity, %{note: note})
933
934 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
935
936 assert activity in activities
937
938 # And check that if the guy we DO follow boosts someone else from their domain,
939 # that should be hidden
940 another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"})
941 bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}})
942 bad_activity = insert(:note_activity, %{note: bad_note})
943 {:ok, repeat_activity} = CommonAPI.repeat(bad_activity.id, domain_user)
944
945 activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
946
947 refute repeat_activity in activities
948 end
949
950 test "returns your own posts regardless of mute" do
951 user = insert(:user)
952 muted = insert(:user)
953
954 {:ok, muted_post} = CommonAPI.post(muted, %{status: "Im stupid"})
955
956 {:ok, reply} =
957 CommonAPI.post(user, %{status: "I'm muting you", in_reply_to_status_id: muted_post.id})
958
959 {:ok, _} = User.mute(user, muted)
960
961 [activity] = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
962
963 assert activity.id == reply.id
964 end
965
966 test "doesn't return muted activities" do
967 activity_one = insert(:note_activity)
968 activity_two = insert(:note_activity)
969 activity_three = insert(:note_activity)
970 user = insert(:user)
971 booster = insert(:user)
972
973 activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
974 {:ok, _user_relationships} = User.mute(user, activity_one_actor)
975
976 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
977
978 assert Enum.member?(activities, activity_two)
979 assert Enum.member?(activities, activity_three)
980 refute Enum.member?(activities, activity_one)
981
982 # Calling with 'with_muted' will deliver muted activities, too.
983 activities =
984 ActivityPub.fetch_activities([], %{
985 muting_user: user,
986 with_muted: true,
987 skip_preload: true
988 })
989
990 assert Enum.member?(activities, activity_two)
991 assert Enum.member?(activities, activity_three)
992 assert Enum.member?(activities, activity_one)
993
994 {:ok, _user_mute} = User.unmute(user, activity_one_actor)
995
996 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
997
998 assert Enum.member?(activities, activity_two)
999 assert Enum.member?(activities, activity_three)
1000 assert Enum.member?(activities, activity_one)
1001
1002 activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
1003 {:ok, _user_relationships} = User.mute(user, activity_three_actor)
1004 {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
1005 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
1006 activity_three = Activity.get_by_id(activity_three.id)
1007
1008 activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
1009
1010 assert Enum.member?(activities, activity_two)
1011 refute Enum.member?(activities, activity_three)
1012 refute Enum.member?(activities, boost_activity)
1013 assert Enum.member?(activities, activity_one)
1014
1015 activities = ActivityPub.fetch_activities([], %{muting_user: nil, skip_preload: true})
1016
1017 assert Enum.member?(activities, activity_two)
1018 assert Enum.member?(activities, activity_three)
1019 assert Enum.member?(activities, boost_activity)
1020 assert Enum.member?(activities, activity_one)
1021 end
1022
1023 test "doesn't return thread muted activities" do
1024 user = insert(:user)
1025 _activity_one = insert(:note_activity)
1026 note_two = insert(:note, data: %{"context" => "suya.."})
1027 activity_two = insert(:note_activity, note: note_two)
1028
1029 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
1030
1031 assert [_activity_one] = ActivityPub.fetch_activities([], %{muting_user: user})
1032 end
1033
1034 test "returns thread muted activities when with_muted is set" do
1035 user = insert(:user)
1036 _activity_one = insert(:note_activity)
1037 note_two = insert(:note, data: %{"context" => "suya.."})
1038 activity_two = insert(:note_activity, note: note_two)
1039
1040 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
1041
1042 assert [_activity_two, _activity_one] =
1043 ActivityPub.fetch_activities([], %{muting_user: user, with_muted: true})
1044 end
1045
1046 test "does include announces on request" do
1047 activity_three = insert(:note_activity)
1048 user = insert(:user)
1049 booster = insert(:user)
1050
1051 {:ok, user, booster} = User.follow(user, booster)
1052
1053 {:ok, announce} = CommonAPI.repeat(activity_three.id, booster)
1054
1055 [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])
1056
1057 assert announce_activity.id == announce.id
1058 end
1059
1060 test "excludes reblogs on request" do
1061 user = insert(:user)
1062 {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
1063 {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
1064
1065 [activity] = ActivityPub.fetch_user_activities(user, nil, %{exclude_reblogs: true})
1066
1067 assert activity == expected_activity
1068 end
1069
1070 describe "irreversible filters" do
1071 setup do
1072 user = insert(:user)
1073 user_two = insert(:user)
1074
1075 insert(:filter, user: user_two, phrase: "cofe", hide: true)
1076 insert(:filter, user: user_two, phrase: "ok boomer", hide: true)
1077 insert(:filter, user: user_two, phrase: "test", hide: false)
1078
1079 params = %{
1080 type: ["Create", "Announce"],
1081 user: user_two
1082 }
1083
1084 {:ok, %{user: user, user_two: user_two, params: params}}
1085 end
1086
1087 test "it returns statuses if they don't contain exact filter words", %{
1088 user: user,
1089 params: params
1090 } do
1091 {:ok, _} = CommonAPI.post(user, %{status: "hey"})
1092 {:ok, _} = CommonAPI.post(user, %{status: "got cofefe?"})
1093 {:ok, _} = CommonAPI.post(user, %{status: "I am not a boomer"})
1094 {:ok, _} = CommonAPI.post(user, %{status: "ok boomers"})
1095 {:ok, _} = CommonAPI.post(user, %{status: "ccofee is not a word"})
1096 {:ok, _} = CommonAPI.post(user, %{status: "this is a test"})
1097
1098 activities = ActivityPub.fetch_activities([], params)
1099
1100 assert Enum.count(activities) == 6
1101 end
1102
1103 test "it does not filter user's own statuses", %{user_two: user_two, params: params} do
1104 {:ok, _} = CommonAPI.post(user_two, %{status: "Give me some cofe!"})
1105 {:ok, _} = CommonAPI.post(user_two, %{status: "ok boomer"})
1106
1107 activities = ActivityPub.fetch_activities([], params)
1108
1109 assert Enum.count(activities) == 2
1110 end
1111
1112 test "it excludes statuses with filter words", %{user: user, params: params} do
1113 {:ok, _} = CommonAPI.post(user, %{status: "Give me some cofe!"})
1114 {:ok, _} = CommonAPI.post(user, %{status: "ok boomer"})
1115 {:ok, _} = CommonAPI.post(user, %{status: "is it a cOfE?"})
1116 {:ok, _} = CommonAPI.post(user, %{status: "cofe is all I need"})
1117 {:ok, _} = CommonAPI.post(user, %{status: "— ok BOOMER\n"})
1118
1119 activities = ActivityPub.fetch_activities([], params)
1120
1121 assert Enum.empty?(activities)
1122 end
1123
1124 test "it returns all statuses if user does not have any filters" do
1125 another_user = insert(:user)
1126 {:ok, _} = CommonAPI.post(another_user, %{status: "got cofe?"})
1127 {:ok, _} = CommonAPI.post(another_user, %{status: "test!"})
1128
1129 activities =
1130 ActivityPub.fetch_activities([], %{
1131 type: ["Create", "Announce"],
1132 user: another_user
1133 })
1134
1135 assert Enum.count(activities) == 2
1136 end
1137 end
1138
1139 describe "public fetch activities" do
1140 test "doesn't retrieve unlisted activities" do
1141 user = insert(:user)
1142
1143 {:ok, _unlisted_activity} = CommonAPI.post(user, %{status: "yeah", visibility: "unlisted"})
1144
1145 {:ok, listed_activity} = CommonAPI.post(user, %{status: "yeah"})
1146
1147 [activity] = ActivityPub.fetch_public_activities()
1148
1149 assert activity == listed_activity
1150 end
1151
1152 test "retrieves public activities" do
1153 _activities = ActivityPub.fetch_public_activities()
1154
1155 %{public: public} = ActivityBuilder.public_and_non_public()
1156
1157 activities = ActivityPub.fetch_public_activities()
1158 assert length(activities) == 1
1159 assert Enum.at(activities, 0) == public
1160 end
1161
1162 test "retrieves a maximum of 20 activities" do
1163 ActivityBuilder.insert_list(10)
1164 expected_activities = ActivityBuilder.insert_list(20)
1165
1166 activities = ActivityPub.fetch_public_activities()
1167
1168 assert collect_ids(activities) == collect_ids(expected_activities)
1169 assert length(activities) == 20
1170 end
1171
1172 test "retrieves ids starting from a since_id" do
1173 activities = ActivityBuilder.insert_list(30)
1174 expected_activities = ActivityBuilder.insert_list(10)
1175 since_id = List.last(activities).id
1176
1177 activities = ActivityPub.fetch_public_activities(%{since_id: since_id})
1178
1179 assert collect_ids(activities) == collect_ids(expected_activities)
1180 assert length(activities) == 10
1181 end
1182
1183 test "retrieves ids up to max_id" do
1184 ActivityBuilder.insert_list(10)
1185 expected_activities = ActivityBuilder.insert_list(20)
1186
1187 %{id: max_id} =
1188 10
1189 |> ActivityBuilder.insert_list()
1190 |> List.first()
1191
1192 activities = ActivityPub.fetch_public_activities(%{max_id: max_id})
1193
1194 assert length(activities) == 20
1195 assert collect_ids(activities) == collect_ids(expected_activities)
1196 end
1197
1198 test "paginates via offset/limit" do
1199 _first_part_activities = ActivityBuilder.insert_list(10)
1200 second_part_activities = ActivityBuilder.insert_list(10)
1201
1202 later_activities = ActivityBuilder.insert_list(10)
1203
1204 activities = ActivityPub.fetch_public_activities(%{page: "2", page_size: "20"}, :offset)
1205
1206 assert length(activities) == 20
1207
1208 assert collect_ids(activities) ==
1209 collect_ids(second_part_activities) ++ collect_ids(later_activities)
1210 end
1211
1212 test "doesn't return reblogs for users for whom reblogs have been muted" do
1213 activity = insert(:note_activity)
1214 user = insert(:user)
1215 booster = insert(:user)
1216 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
1217
1218 {:ok, activity} = CommonAPI.repeat(activity.id, booster)
1219
1220 activities = ActivityPub.fetch_activities([], %{muting_user: user})
1221
1222 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
1223 end
1224
1225 test "returns reblogs for users for whom reblogs have not been muted" do
1226 activity = insert(:note_activity)
1227 user = insert(:user)
1228 booster = insert(:user)
1229 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
1230 {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
1231
1232 {:ok, activity} = CommonAPI.repeat(activity.id, booster)
1233
1234 activities = ActivityPub.fetch_activities([], %{muting_user: user})
1235
1236 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
1237 end
1238 end
1239
1240 describe "uploading files" do
1241 setup do
1242 test_file = %Plug.Upload{
1243 content_type: "image/jpeg",
1244 path: Path.absname("test/fixtures/image.jpg"),
1245 filename: "an_image.jpg"
1246 }
1247
1248 %{test_file: test_file}
1249 end
1250
1251 test "sets a description if given", %{test_file: file} do
1252 {:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file")
1253 assert object.data["name"] == "a cool file"
1254 end
1255
1256 test "it sets the default description depending on the configuration", %{test_file: file} do
1257 clear_config([Pleroma.Upload, :default_description])
1258
1259 clear_config([Pleroma.Upload, :default_description], nil)
1260 {:ok, %Object{} = object} = ActivityPub.upload(file)
1261 assert object.data["name"] == ""
1262
1263 clear_config([Pleroma.Upload, :default_description], :filename)
1264 {:ok, %Object{} = object} = ActivityPub.upload(file)
1265 assert object.data["name"] == "an_image.jpg"
1266
1267 clear_config([Pleroma.Upload, :default_description], "unnamed attachment")
1268 {:ok, %Object{} = object} = ActivityPub.upload(file)
1269 assert object.data["name"] == "unnamed attachment"
1270 end
1271
1272 test "copies the file to the configured folder", %{test_file: file} do
1273 clear_config([Pleroma.Upload, :default_description], :filename)
1274 {:ok, %Object{} = object} = ActivityPub.upload(file)
1275 assert object.data["name"] == "an_image.jpg"
1276 end
1277
1278 test "works with base64 encoded images" do
1279 file = %{
1280 img: data_uri()
1281 }
1282
1283 {:ok, %Object{}} = ActivityPub.upload(file)
1284 end
1285 end
1286
1287 describe "fetch the latest Follow" do
1288 test "fetches the latest Follow activity" do
1289 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
1290 follower = Repo.get_by(User, ap_id: activity.data["actor"])
1291 followed = Repo.get_by(User, ap_id: activity.data["object"])
1292
1293 assert activity == Utils.fetch_latest_follow(follower, followed)
1294 end
1295 end
1296
1297 describe "unfollowing" do
1298 test "it reverts unfollow activity" do
1299 follower = insert(:user)
1300 followed = insert(:user)
1301
1302 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1303
1304 with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
1305 assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
1306 end
1307
1308 activity = Activity.get_by_id(follow_activity.id)
1309 assert activity.data["type"] == "Follow"
1310 assert activity.data["actor"] == follower.ap_id
1311
1312 assert activity.data["object"] == followed.ap_id
1313 end
1314
1315 test "creates an undo activity for the last follow" do
1316 follower = insert(:user)
1317 followed = insert(:user)
1318
1319 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1320 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1321
1322 assert activity.data["type"] == "Undo"
1323 assert activity.data["actor"] == follower.ap_id
1324
1325 embedded_object = activity.data["object"]
1326 assert is_map(embedded_object)
1327 assert embedded_object["type"] == "Follow"
1328 assert embedded_object["object"] == followed.ap_id
1329 assert embedded_object["id"] == follow_activity.data["id"]
1330 end
1331
1332 test "creates an undo activity for a pending follow request" do
1333 follower = insert(:user)
1334 followed = insert(:user, %{is_locked: true})
1335
1336 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
1337 {:ok, activity} = ActivityPub.unfollow(follower, followed)
1338
1339 assert activity.data["type"] == "Undo"
1340 assert activity.data["actor"] == follower.ap_id
1341
1342 embedded_object = activity.data["object"]
1343 assert is_map(embedded_object)
1344 assert embedded_object["type"] == "Follow"
1345 assert embedded_object["object"] == followed.ap_id
1346 assert embedded_object["id"] == follow_activity.data["id"]
1347 end
1348 end
1349
1350 describe "timeline post-processing" do
1351 test "it filters broken threads" do
1352 user1 = insert(:user)
1353 user2 = insert(:user)
1354 user3 = insert(:user)
1355
1356 {:ok, user1, user3} = User.follow(user1, user3)
1357 assert User.following?(user1, user3)
1358
1359 {:ok, user2, user3} = User.follow(user2, user3)
1360 assert User.following?(user2, user3)
1361
1362 {:ok, user3, user2} = User.follow(user3, user2)
1363 assert User.following?(user3, user2)
1364
1365 {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"})
1366
1367 {:ok, private_activity_1} = CommonAPI.post(user3, %{status: "hi 2", visibility: "private"})
1368
1369 {:ok, private_activity_2} =
1370 CommonAPI.post(user2, %{
1371 status: "hi 3",
1372 visibility: "private",
1373 in_reply_to_status_id: private_activity_1.id
1374 })
1375
1376 {:ok, private_activity_3} =
1377 CommonAPI.post(user3, %{
1378 status: "hi 4",
1379 visibility: "private",
1380 in_reply_to_status_id: private_activity_2.id
1381 })
1382
1383 activities =
1384 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
1385 |> Enum.map(fn a -> a.id end)
1386
1387 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1388
1389 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1390
1391 assert length(activities) == 3
1392
1393 activities =
1394 ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{user: user1})
1395 |> Enum.map(fn a -> a.id end)
1396
1397 assert [public_activity.id, private_activity_1.id] == activities
1398 assert length(activities) == 2
1399 end
1400 end
1401
1402 describe "flag/1" do
1403 setup do
1404 reporter = insert(:user)
1405 target_account = insert(:user)
1406 content = "foobar"
1407 {:ok, activity} = CommonAPI.post(target_account, %{status: content})
1408 context = Utils.generate_context_id()
1409
1410 reporter_ap_id = reporter.ap_id
1411 target_ap_id = target_account.ap_id
1412 activity_ap_id = activity.data["id"]
1413
1414 activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
1415
1416 {:ok,
1417 %{
1418 reporter: reporter,
1419 context: context,
1420 target_account: target_account,
1421 reported_activity: activity,
1422 content: content,
1423 activity_ap_id: activity_ap_id,
1424 activity_with_object: activity_with_object,
1425 reporter_ap_id: reporter_ap_id,
1426 target_ap_id: target_ap_id
1427 }}
1428 end
1429
1430 test "it can create a Flag activity",
1431 %{
1432 reporter: reporter,
1433 context: context,
1434 target_account: target_account,
1435 reported_activity: reported_activity,
1436 content: content,
1437 activity_ap_id: activity_ap_id,
1438 activity_with_object: activity_with_object,
1439 reporter_ap_id: reporter_ap_id,
1440 target_ap_id: target_ap_id
1441 } do
1442 assert {:ok, activity} =
1443 ActivityPub.flag(%{
1444 actor: reporter,
1445 context: context,
1446 account: target_account,
1447 statuses: [reported_activity],
1448 content: content
1449 })
1450
1451 note_obj = %{
1452 "type" => "Note",
1453 "id" => activity_ap_id,
1454 "content" => content,
1455 "published" => activity_with_object.object.data["published"],
1456 "actor" =>
1457 AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
1458 }
1459
1460 assert %Activity{
1461 actor: ^reporter_ap_id,
1462 data: %{
1463 "type" => "Flag",
1464 "content" => ^content,
1465 "context" => ^context,
1466 "object" => [^target_ap_id, ^note_obj]
1467 }
1468 } = activity
1469 end
1470
1471 test_with_mock "strips status data from Flag, before federating it",
1472 %{
1473 reporter: reporter,
1474 context: context,
1475 target_account: target_account,
1476 reported_activity: reported_activity,
1477 content: content
1478 },
1479 Utils,
1480 [:passthrough],
1481 [] do
1482 {:ok, activity} =
1483 ActivityPub.flag(%{
1484 actor: reporter,
1485 context: context,
1486 account: target_account,
1487 statuses: [reported_activity],
1488 content: content
1489 })
1490
1491 new_data =
1492 put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]])
1493
1494 assert_called(Utils.maybe_federate(%{activity | data: new_data}))
1495 end
1496
1497 test_with_mock "reverts on error",
1498 %{
1499 reporter: reporter,
1500 context: context,
1501 target_account: target_account,
1502 reported_activity: reported_activity,
1503 content: content
1504 },
1505 Utils,
1506 [:passthrough],
1507 maybe_federate: fn _ -> {:error, :reverted} end do
1508 assert {:error, :reverted} =
1509 ActivityPub.flag(%{
1510 actor: reporter,
1511 context: context,
1512 account: target_account,
1513 statuses: [reported_activity],
1514 content: content
1515 })
1516
1517 assert Repo.aggregate(Activity, :count, :id) == 1
1518 assert Repo.aggregate(Object, :count, :id) == 2
1519 assert Repo.aggregate(Notification, :count, :id) == 0
1520 end
1521 end
1522
1523 test "fetch_activities/2 returns activities addressed to a list " do
1524 user = insert(:user)
1525 member = insert(:user)
1526 {:ok, list} = Pleroma.List.create("foo", user)
1527 {:ok, list} = Pleroma.List.follow(list, member)
1528
1529 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1530
1531 activity = Repo.preload(activity, :bookmark)
1532 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1533
1534 assert ActivityPub.fetch_activities([], %{user: user}) == [activity]
1535 end
1536
1537 def data_uri do
1538 File.read!("test/fixtures/avatar_data_uri")
1539 end
1540
1541 describe "fetch_activities_bounded" do
1542 test "fetches private posts for followed users" do
1543 user = insert(:user)
1544
1545 {:ok, activity} =
1546 CommonAPI.post(user, %{
1547 status: "thought I looked cute might delete later :3",
1548 visibility: "private"
1549 })
1550
1551 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1552 assert result.id == activity.id
1553 end
1554
1555 test "fetches only public posts for other users" do
1556 user = insert(:user)
1557 {:ok, activity} = CommonAPI.post(user, %{status: "#cofe", visibility: "public"})
1558
1559 {:ok, _private_activity} =
1560 CommonAPI.post(user, %{
1561 status: "why is tenshi eating a corndog so cute?",
1562 visibility: "private"
1563 })
1564
1565 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1566 assert result.id == activity.id
1567 end
1568 end
1569
1570 describe "fetch_follow_information_for_user" do
1571 test "syncronizes following/followers counters" do
1572 user =
1573 insert(:user,
1574 local: false,
1575 follower_address: "http://localhost:4001/users/fuser2/followers",
1576 following_address: "http://localhost:4001/users/fuser2/following"
1577 )
1578
1579 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1580 assert info.follower_count == 527
1581 assert info.following_count == 267
1582 end
1583
1584 test "detects hidden followers" do
1585 mock(fn env ->
1586 case env.url do
1587 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1588 %Tesla.Env{status: 403, body: ""}
1589
1590 _ ->
1591 apply(HttpRequestMock, :request, [env])
1592 end
1593 end)
1594
1595 user =
1596 insert(:user,
1597 local: false,
1598 follower_address: "http://localhost:4001/users/masto_closed/followers",
1599 following_address: "http://localhost:4001/users/masto_closed/following"
1600 )
1601
1602 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1603 assert follow_info.hide_followers == true
1604 assert follow_info.hide_follows == false
1605 end
1606
1607 test "detects hidden follows" do
1608 mock(fn env ->
1609 case env.url do
1610 "http://localhost:4001/users/masto_closed/following?page=1" ->
1611 %Tesla.Env{status: 403, body: ""}
1612
1613 _ ->
1614 apply(HttpRequestMock, :request, [env])
1615 end
1616 end)
1617
1618 user =
1619 insert(:user,
1620 local: false,
1621 follower_address: "http://localhost:4001/users/masto_closed/followers",
1622 following_address: "http://localhost:4001/users/masto_closed/following"
1623 )
1624
1625 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1626 assert follow_info.hide_followers == false
1627 assert follow_info.hide_follows == true
1628 end
1629
1630 test "detects hidden follows/followers for friendica" do
1631 user =
1632 insert(:user,
1633 local: false,
1634 follower_address: "http://localhost:8080/followers/fuser3",
1635 following_address: "http://localhost:8080/following/fuser3"
1636 )
1637
1638 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1639 assert follow_info.hide_followers == true
1640 assert follow_info.follower_count == 296
1641 assert follow_info.following_count == 32
1642 assert follow_info.hide_follows == true
1643 end
1644
1645 test "doesn't crash when follower and following counters are hidden" do
1646 mock(fn env ->
1647 case env.url do
1648 "http://localhost:4001/users/masto_hidden_counters/following" ->
1649 json(
1650 %{
1651 "@context" => "https://www.w3.org/ns/activitystreams",
1652 "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
1653 },
1654 headers: HttpRequestMock.activitypub_object_headers()
1655 )
1656
1657 "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
1658 %Tesla.Env{status: 403, body: ""}
1659
1660 "http://localhost:4001/users/masto_hidden_counters/followers" ->
1661 json(
1662 %{
1663 "@context" => "https://www.w3.org/ns/activitystreams",
1664 "id" => "http://localhost:4001/users/masto_hidden_counters/following"
1665 },
1666 headers: HttpRequestMock.activitypub_object_headers()
1667 )
1668
1669 "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
1670 %Tesla.Env{status: 403, body: ""}
1671 end
1672 end)
1673
1674 user =
1675 insert(:user,
1676 local: false,
1677 follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
1678 following_address: "http://localhost:4001/users/masto_hidden_counters/following"
1679 )
1680
1681 {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
1682
1683 assert follow_info.hide_followers == true
1684 assert follow_info.follower_count == 0
1685 assert follow_info.hide_follows == true
1686 assert follow_info.following_count == 0
1687 end
1688 end
1689
1690 describe "fetch_favourites/3" do
1691 test "returns a favourite activities sorted by adds to favorite" do
1692 user = insert(:user)
1693 other_user = insert(:user)
1694 user1 = insert(:user)
1695 user2 = insert(:user)
1696 {:ok, a1} = CommonAPI.post(user1, %{status: "bla"})
1697 {:ok, _a2} = CommonAPI.post(user2, %{status: "traps are happy"})
1698 {:ok, a3} = CommonAPI.post(user2, %{status: "Trees Are "})
1699 {:ok, a4} = CommonAPI.post(user2, %{status: "Agent Smith "})
1700 {:ok, a5} = CommonAPI.post(user1, %{status: "Red or Blue "})
1701
1702 {:ok, _} = CommonAPI.favorite(user, a4.id)
1703 {:ok, _} = CommonAPI.favorite(other_user, a3.id)
1704 {:ok, _} = CommonAPI.favorite(user, a3.id)
1705 {:ok, _} = CommonAPI.favorite(other_user, a5.id)
1706 {:ok, _} = CommonAPI.favorite(user, a5.id)
1707 {:ok, _} = CommonAPI.favorite(other_user, a4.id)
1708 {:ok, _} = CommonAPI.favorite(user, a1.id)
1709 {:ok, _} = CommonAPI.favorite(other_user, a1.id)
1710 result = ActivityPub.fetch_favourites(user)
1711
1712 assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
1713
1714 result = ActivityPub.fetch_favourites(user, %{limit: 2})
1715 assert Enum.map(result, & &1.id) == [a1.id, a5.id]
1716 end
1717 end
1718
1719 describe "Move activity" do
1720 test "create" do
1721 %{ap_id: old_ap_id} = old_user = insert(:user)
1722 %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
1723 follower = insert(:user)
1724 follower_move_opted_out = insert(:user, allow_following_move: false)
1725
1726 User.follow(follower, old_user)
1727 User.follow(follower_move_opted_out, old_user)
1728
1729 assert User.following?(follower, old_user)
1730 assert User.following?(follower_move_opted_out, old_user)
1731
1732 assert {:ok, activity} = ActivityPub.move(old_user, new_user)
1733
1734 assert %Activity{
1735 actor: ^old_ap_id,
1736 data: %{
1737 "actor" => ^old_ap_id,
1738 "object" => ^old_ap_id,
1739 "target" => ^new_ap_id,
1740 "type" => "Move"
1741 },
1742 local: true
1743 } = activity
1744
1745 params = %{
1746 "op" => "move_following",
1747 "origin_id" => old_user.id,
1748 "target_id" => new_user.id
1749 }
1750
1751 assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
1752
1753 Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
1754
1755 refute User.following?(follower, old_user)
1756 assert User.following?(follower, new_user)
1757
1758 assert User.following?(follower_move_opted_out, old_user)
1759 refute User.following?(follower_move_opted_out, new_user)
1760
1761 activity = %Activity{activity | object: nil}
1762
1763 assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
1764
1765 assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
1766 end
1767
1768 test "old user must be in the new user's `also_known_as` list" do
1769 old_user = insert(:user)
1770 new_user = insert(:user)
1771
1772 assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
1773 ActivityPub.move(old_user, new_user)
1774 end
1775 end
1776
1777 test "doesn't retrieve replies activities with exclude_replies" do
1778 user = insert(:user)
1779
1780 {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
1781
1782 {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id})
1783
1784 [result] = ActivityPub.fetch_public_activities(%{exclude_replies: true})
1785
1786 assert result.id == activity.id
1787
1788 assert length(ActivityPub.fetch_public_activities()) == 2
1789 end
1790
1791 describe "replies filtering with public messages" do
1792 setup :public_messages
1793
1794 test "public timeline", %{users: %{u1: user}} do
1795 activities_ids =
1796 %{}
1797 |> Map.put(:type, ["Create", "Announce"])
1798 |> Map.put(:local_only, false)
1799 |> Map.put(:blocking_user, user)
1800 |> Map.put(:muting_user, user)
1801 |> Map.put(:reply_filtering_user, user)
1802 |> ActivityPub.fetch_public_activities()
1803 |> Enum.map(& &1.id)
1804
1805 assert length(activities_ids) == 16
1806 end
1807
1808 test "public timeline with reply_visibility `following`", %{
1809 users: %{u1: user},
1810 u1: u1,
1811 u2: u2,
1812 u3: u3,
1813 u4: u4,
1814 activities: activities
1815 } do
1816 activities_ids =
1817 %{}
1818 |> Map.put(:type, ["Create", "Announce"])
1819 |> Map.put(:local_only, false)
1820 |> Map.put(:blocking_user, user)
1821 |> Map.put(:muting_user, user)
1822 |> Map.put(:reply_visibility, "following")
1823 |> Map.put(:reply_filtering_user, user)
1824 |> ActivityPub.fetch_public_activities()
1825 |> Enum.map(& &1.id)
1826
1827 assert length(activities_ids) == 14
1828
1829 visible_ids =
1830 Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
1831
1832 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1833 end
1834
1835 test "public timeline with reply_visibility `self`", %{
1836 users: %{u1: user},
1837 u1: u1,
1838 u2: u2,
1839 u3: u3,
1840 u4: u4,
1841 activities: activities
1842 } do
1843 activities_ids =
1844 %{}
1845 |> Map.put(:type, ["Create", "Announce"])
1846 |> Map.put(:local_only, false)
1847 |> Map.put(:blocking_user, user)
1848 |> Map.put(:muting_user, user)
1849 |> Map.put(:reply_visibility, "self")
1850 |> Map.put(:reply_filtering_user, user)
1851 |> ActivityPub.fetch_public_activities()
1852 |> Enum.map(& &1.id)
1853
1854 assert length(activities_ids) == 10
1855 visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
1856 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1857 end
1858
1859 test "home timeline", %{
1860 users: %{u1: user},
1861 activities: activities,
1862 u1: u1,
1863 u2: u2,
1864 u3: u3,
1865 u4: u4
1866 } do
1867 params =
1868 %{}
1869 |> Map.put(:type, ["Create", "Announce"])
1870 |> Map.put(:blocking_user, user)
1871 |> Map.put(:muting_user, user)
1872 |> Map.put(:user, user)
1873 |> Map.put(:reply_filtering_user, user)
1874
1875 activities_ids =
1876 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1877 |> Enum.map(& &1.id)
1878
1879 assert length(activities_ids) == 13
1880
1881 visible_ids =
1882 Map.values(u1) ++
1883 Map.values(u3) ++
1884 [
1885 activities[:a1],
1886 activities[:a2],
1887 activities[:a4],
1888 u2[:r1],
1889 u2[:r3],
1890 u4[:r1],
1891 u4[:r2]
1892 ]
1893
1894 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1895 end
1896
1897 test "home timeline with reply_visibility `following`", %{
1898 users: %{u1: user},
1899 activities: activities,
1900 u1: u1,
1901 u2: u2,
1902 u3: u3,
1903 u4: u4
1904 } do
1905 params =
1906 %{}
1907 |> Map.put(:type, ["Create", "Announce"])
1908 |> Map.put(:blocking_user, user)
1909 |> Map.put(:muting_user, user)
1910 |> Map.put(:user, user)
1911 |> Map.put(:reply_visibility, "following")
1912 |> Map.put(:reply_filtering_user, user)
1913
1914 activities_ids =
1915 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1916 |> Enum.map(& &1.id)
1917
1918 assert length(activities_ids) == 11
1919
1920 visible_ids =
1921 Map.values(u1) ++
1922 [
1923 activities[:a1],
1924 activities[:a2],
1925 activities[:a4],
1926 u2[:r1],
1927 u2[:r3],
1928 u3[:r1],
1929 u4[:r1],
1930 u4[:r2]
1931 ]
1932
1933 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1934 end
1935
1936 test "home timeline with reply_visibility `self`", %{
1937 users: %{u1: user},
1938 activities: activities,
1939 u1: u1,
1940 u2: u2,
1941 u3: u3,
1942 u4: u4
1943 } do
1944 params =
1945 %{}
1946 |> Map.put(:type, ["Create", "Announce"])
1947 |> Map.put(:blocking_user, user)
1948 |> Map.put(:muting_user, user)
1949 |> Map.put(:user, user)
1950 |> Map.put(:reply_visibility, "self")
1951 |> Map.put(:reply_filtering_user, user)
1952
1953 activities_ids =
1954 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
1955 |> Enum.map(& &1.id)
1956
1957 assert length(activities_ids) == 9
1958
1959 visible_ids =
1960 Map.values(u1) ++
1961 [
1962 activities[:a1],
1963 activities[:a2],
1964 activities[:a4],
1965 u2[:r1],
1966 u3[:r1],
1967 u4[:r1]
1968 ]
1969
1970 assert Enum.all?(visible_ids, &(&1 in activities_ids))
1971 end
1972
1973 test "filtering out announces where the user is the actor of the announced message" do
1974 user = insert(:user)
1975 other_user = insert(:user)
1976 third_user = insert(:user)
1977 User.follow(user, other_user)
1978
1979 {:ok, post} = CommonAPI.post(user, %{status: "yo"})
1980 {:ok, other_post} = CommonAPI.post(third_user, %{status: "yo"})
1981 {:ok, _announce} = CommonAPI.repeat(post.id, other_user)
1982 {:ok, _announce} = CommonAPI.repeat(post.id, third_user)
1983 {:ok, announce} = CommonAPI.repeat(other_post.id, other_user)
1984
1985 params = %{
1986 type: ["Announce"]
1987 }
1988
1989 results =
1990 [user.ap_id | User.following(user)]
1991 |> ActivityPub.fetch_activities(params)
1992
1993 assert length(results) == 3
1994
1995 params = %{
1996 type: ["Announce"],
1997 announce_filtering_user: user
1998 }
1999
2000 [result] =
2001 [user.ap_id | User.following(user)]
2002 |> ActivityPub.fetch_activities(params)
2003
2004 assert result.id == announce.id
2005 end
2006 end
2007
2008 describe "replies filtering with private messages" do
2009 setup :private_messages
2010
2011 test "public timeline", %{users: %{u1: user}} do
2012 activities_ids =
2013 %{}
2014 |> Map.put(:type, ["Create", "Announce"])
2015 |> Map.put(:local_only, false)
2016 |> Map.put(:blocking_user, user)
2017 |> Map.put(:muting_user, user)
2018 |> Map.put(:user, user)
2019 |> ActivityPub.fetch_public_activities()
2020 |> Enum.map(& &1.id)
2021
2022 assert activities_ids == []
2023 end
2024
2025 test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2026 activities_ids =
2027 %{}
2028 |> Map.put(:type, ["Create", "Announce"])
2029 |> Map.put(:local_only, false)
2030 |> Map.put(:blocking_user, user)
2031 |> Map.put(:muting_user, user)
2032 |> Map.put(:reply_visibility, "following")
2033 |> Map.put(:reply_filtering_user, user)
2034 |> Map.put(:user, user)
2035 |> ActivityPub.fetch_public_activities()
2036 |> Enum.map(& &1.id)
2037
2038 assert activities_ids == []
2039 end
2040
2041 test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
2042 activities_ids =
2043 %{}
2044 |> Map.put(:type, ["Create", "Announce"])
2045 |> Map.put(:local_only, false)
2046 |> Map.put(:blocking_user, user)
2047 |> Map.put(:muting_user, user)
2048 |> Map.put(:reply_visibility, "self")
2049 |> Map.put(:reply_filtering_user, user)
2050 |> Map.put(:user, user)
2051 |> ActivityPub.fetch_public_activities()
2052 |> Enum.map(& &1.id)
2053
2054 assert activities_ids == []
2055
2056 activities_ids =
2057 %{}
2058 |> Map.put(:reply_visibility, "self")
2059 |> Map.put(:reply_filtering_user, nil)
2060 |> ActivityPub.fetch_public_activities()
2061
2062 assert activities_ids == []
2063 end
2064
2065 test "home timeline", %{users: %{u1: user}} do
2066 params =
2067 %{}
2068 |> Map.put(:type, ["Create", "Announce"])
2069 |> Map.put(:blocking_user, user)
2070 |> Map.put(:muting_user, user)
2071 |> Map.put(:user, user)
2072
2073 activities_ids =
2074 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2075 |> Enum.map(& &1.id)
2076
2077 assert length(activities_ids) == 12
2078 end
2079
2080 test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
2081 params =
2082 %{}
2083 |> Map.put(:type, ["Create", "Announce"])
2084 |> Map.put(:blocking_user, user)
2085 |> Map.put(:muting_user, user)
2086 |> Map.put(:user, user)
2087 |> Map.put(:reply_visibility, "following")
2088 |> Map.put(:reply_filtering_user, user)
2089
2090 activities_ids =
2091 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2092 |> Enum.map(& &1.id)
2093
2094 assert length(activities_ids) == 12
2095 end
2096
2097 test "home timeline with default reply_visibility `self`", %{
2098 users: %{u1: user},
2099 activities: activities,
2100 u1: u1,
2101 u2: u2,
2102 u3: u3,
2103 u4: u4
2104 } do
2105 params =
2106 %{}
2107 |> Map.put(:type, ["Create", "Announce"])
2108 |> Map.put(:blocking_user, user)
2109 |> Map.put(:muting_user, user)
2110 |> Map.put(:user, user)
2111 |> Map.put(:reply_visibility, "self")
2112 |> Map.put(:reply_filtering_user, user)
2113
2114 activities_ids =
2115 ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
2116 |> Enum.map(& &1.id)
2117
2118 assert length(activities_ids) == 10
2119
2120 visible_ids =
2121 Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
2122
2123 assert Enum.all?(visible_ids, &(&1 in activities_ids))
2124 end
2125 end
2126
2127 defp public_messages(_) do
2128 [u1, u2, u3, u4] = insert_list(4, :user)
2129 {:ok, u1, u2} = User.follow(u1, u2)
2130 {:ok, u2, u1} = User.follow(u2, u1)
2131 {:ok, u1, u4} = User.follow(u1, u4)
2132 {:ok, u4, u1} = User.follow(u4, u1)
2133
2134 {:ok, u2, u3} = User.follow(u2, u3)
2135 {:ok, u3, u2} = User.follow(u3, u2)
2136
2137 {:ok, a1} = CommonAPI.post(u1, %{status: "Status"})
2138
2139 {:ok, r1_1} =
2140 CommonAPI.post(u2, %{
2141 status: "@#{u1.nickname} reply from u2 to u1",
2142 in_reply_to_status_id: a1.id
2143 })
2144
2145 {:ok, r1_2} =
2146 CommonAPI.post(u3, %{
2147 status: "@#{u1.nickname} reply from u3 to u1",
2148 in_reply_to_status_id: a1.id
2149 })
2150
2151 {:ok, r1_3} =
2152 CommonAPI.post(u4, %{
2153 status: "@#{u1.nickname} reply from u4 to u1",
2154 in_reply_to_status_id: a1.id
2155 })
2156
2157 {:ok, a2} = CommonAPI.post(u2, %{status: "Status"})
2158
2159 {:ok, r2_1} =
2160 CommonAPI.post(u1, %{
2161 status: "@#{u2.nickname} reply from u1 to u2",
2162 in_reply_to_status_id: a2.id
2163 })
2164
2165 {:ok, r2_2} =
2166 CommonAPI.post(u3, %{
2167 status: "@#{u2.nickname} reply from u3 to u2",
2168 in_reply_to_status_id: a2.id
2169 })
2170
2171 {:ok, r2_3} =
2172 CommonAPI.post(u4, %{
2173 status: "@#{u2.nickname} reply from u4 to u2",
2174 in_reply_to_status_id: a2.id
2175 })
2176
2177 {:ok, a3} = CommonAPI.post(u3, %{status: "Status"})
2178
2179 {:ok, r3_1} =
2180 CommonAPI.post(u1, %{
2181 status: "@#{u3.nickname} reply from u1 to u3",
2182 in_reply_to_status_id: a3.id
2183 })
2184
2185 {:ok, r3_2} =
2186 CommonAPI.post(u2, %{
2187 status: "@#{u3.nickname} reply from u2 to u3",
2188 in_reply_to_status_id: a3.id
2189 })
2190
2191 {:ok, r3_3} =
2192 CommonAPI.post(u4, %{
2193 status: "@#{u3.nickname} reply from u4 to u3",
2194 in_reply_to_status_id: a3.id
2195 })
2196
2197 {:ok, a4} = CommonAPI.post(u4, %{status: "Status"})
2198
2199 {:ok, r4_1} =
2200 CommonAPI.post(u1, %{
2201 status: "@#{u4.nickname} reply from u1 to u4",
2202 in_reply_to_status_id: a4.id
2203 })
2204
2205 {:ok, r4_2} =
2206 CommonAPI.post(u2, %{
2207 status: "@#{u4.nickname} reply from u2 to u4",
2208 in_reply_to_status_id: a4.id
2209 })
2210
2211 {:ok, r4_3} =
2212 CommonAPI.post(u3, %{
2213 status: "@#{u4.nickname} reply from u3 to u4",
2214 in_reply_to_status_id: a4.id
2215 })
2216
2217 {:ok,
2218 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2219 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2220 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2221 u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
2222 u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
2223 u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
2224 end
2225
2226 defp private_messages(_) do
2227 [u1, u2, u3, u4] = insert_list(4, :user)
2228 {:ok, u1, u2} = User.follow(u1, u2)
2229 {:ok, u2, u1} = User.follow(u2, u1)
2230 {:ok, u1, u3} = User.follow(u1, u3)
2231 {:ok, u3, u1} = User.follow(u3, u1)
2232 {:ok, u1, u4} = User.follow(u1, u4)
2233 {:ok, u4, u1} = User.follow(u4, u1)
2234
2235 {:ok, u2, u3} = User.follow(u2, u3)
2236 {:ok, u3, u2} = User.follow(u3, u2)
2237
2238 {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"})
2239
2240 {:ok, r1_1} =
2241 CommonAPI.post(u2, %{
2242 status: "@#{u1.nickname} reply from u2 to u1",
2243 in_reply_to_status_id: a1.id,
2244 visibility: "private"
2245 })
2246
2247 {:ok, r1_2} =
2248 CommonAPI.post(u3, %{
2249 status: "@#{u1.nickname} reply from u3 to u1",
2250 in_reply_to_status_id: a1.id,
2251 visibility: "private"
2252 })
2253
2254 {:ok, r1_3} =
2255 CommonAPI.post(u4, %{
2256 status: "@#{u1.nickname} reply from u4 to u1",
2257 in_reply_to_status_id: a1.id,
2258 visibility: "private"
2259 })
2260
2261 {:ok, a2} = CommonAPI.post(u2, %{status: "Status", visibility: "private"})
2262
2263 {:ok, r2_1} =
2264 CommonAPI.post(u1, %{
2265 status: "@#{u2.nickname} reply from u1 to u2",
2266 in_reply_to_status_id: a2.id,
2267 visibility: "private"
2268 })
2269
2270 {:ok, r2_2} =
2271 CommonAPI.post(u3, %{
2272 status: "@#{u2.nickname} reply from u3 to u2",
2273 in_reply_to_status_id: a2.id,
2274 visibility: "private"
2275 })
2276
2277 {:ok, a3} = CommonAPI.post(u3, %{status: "Status", visibility: "private"})
2278
2279 {:ok, r3_1} =
2280 CommonAPI.post(u1, %{
2281 status: "@#{u3.nickname} reply from u1 to u3",
2282 in_reply_to_status_id: a3.id,
2283 visibility: "private"
2284 })
2285
2286 {:ok, r3_2} =
2287 CommonAPI.post(u2, %{
2288 status: "@#{u3.nickname} reply from u2 to u3",
2289 in_reply_to_status_id: a3.id,
2290 visibility: "private"
2291 })
2292
2293 {:ok, a4} = CommonAPI.post(u4, %{status: "Status", visibility: "private"})
2294
2295 {:ok, r4_1} =
2296 CommonAPI.post(u1, %{
2297 status: "@#{u4.nickname} reply from u1 to u4",
2298 in_reply_to_status_id: a4.id,
2299 visibility: "private"
2300 })
2301
2302 {:ok,
2303 users: %{u1: u1, u2: u2, u3: u3, u4: u4},
2304 activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
2305 u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
2306 u2: %{r1: r2_1.id, r2: r2_2.id},
2307 u3: %{r1: r3_1.id, r2: r3_2.id},
2308 u4: %{r1: r4_1.id}}
2309 end
2310
2311 describe "maybe_update_follow_information/1" do
2312 setup do
2313 clear_config([:instance, :external_user_synchronization], true)
2314
2315 user = %{
2316 local: false,
2317 ap_id: "https://gensokyo.2hu/users/raymoo",
2318 following_address: "https://gensokyo.2hu/users/following",
2319 follower_address: "https://gensokyo.2hu/users/followers",
2320 type: "Person"
2321 }
2322
2323 %{user: user}
2324 end
2325
2326 test "logs an error when it can't fetch the info", %{user: user} do
2327 assert capture_log(fn ->
2328 ActivityPub.maybe_update_follow_information(user)
2329 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2330 end
2331
2332 test "just returns the input if the user type is Application", %{
2333 user: user
2334 } do
2335 user =
2336 user
2337 |> Map.put(:type, "Application")
2338
2339 refute capture_log(fn ->
2340 assert ^user = ActivityPub.maybe_update_follow_information(user)
2341 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2342 end
2343
2344 test "it just returns the input if the user has no following/follower addresses", %{
2345 user: user
2346 } do
2347 user =
2348 user
2349 |> Map.put(:following_address, nil)
2350 |> Map.put(:follower_address, nil)
2351
2352 refute capture_log(fn ->
2353 assert ^user = ActivityPub.maybe_update_follow_information(user)
2354 end) =~ "Follower/Following counter update for #{user.ap_id} failed"
2355 end
2356 end
2357
2358 describe "global activity expiration" do
2359 test "creates an activity expiration for local Create activities" do
2360 clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy)
2361
2362 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
2363 {:ok, follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
2364
2365 assert_enqueued(
2366 worker: Pleroma.Workers.PurgeExpiredActivity,
2367 args: %{activity_id: activity.id},
2368 scheduled_at:
2369 activity.inserted_at
2370 |> DateTime.from_naive!("Etc/UTC")
2371 |> Timex.shift(days: 365)
2372 )
2373
2374 refute_enqueued(
2375 worker: Pleroma.Workers.PurgeExpiredActivity,
2376 args: %{activity_id: follow.id}
2377 )
2378 end
2379 end
2380
2381 describe "handling of clashing nicknames" do
2382 test "renames an existing user with a clashing nickname and a different ap id" do
2383 orig_user =
2384 insert(
2385 :user,
2386 local: false,
2387 nickname: "admin@mastodon.example.org",
2388 ap_id: "http://mastodon.example.org/users/harinezumigari"
2389 )
2390
2391 %{
2392 nickname: orig_user.nickname,
2393 ap_id: orig_user.ap_id <> "part_2"
2394 }
2395 |> ActivityPub.maybe_handle_clashing_nickname()
2396
2397 user = User.get_by_id(orig_user.id)
2398
2399 assert user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
2400 end
2401
2402 test "does nothing with a clashing nickname and the same ap id" do
2403 orig_user =
2404 insert(
2405 :user,
2406 local: false,
2407 nickname: "admin@mastodon.example.org",
2408 ap_id: "http://mastodon.example.org/users/harinezumigari"
2409 )
2410
2411 %{
2412 nickname: orig_user.nickname,
2413 ap_id: orig_user.ap_id
2414 }
2415 |> ActivityPub.maybe_handle_clashing_nickname()
2416
2417 user = User.get_by_id(orig_user.id)
2418
2419 assert user.nickname == orig_user.nickname
2420 end
2421 end
2422
2423 describe "reply filtering" do
2424 test "`following` still contains announcements by friends" do
2425 user = insert(:user)
2426 followed = insert(:user)
2427 not_followed = insert(:user)
2428
2429 User.follow(user, followed)
2430
2431 {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2432
2433 {:ok, not_followed_to_followed} =
2434 CommonAPI.post(not_followed, %{
2435 status: "Also hello",
2436 in_reply_to_status_id: followed_post.id
2437 })
2438
2439 {:ok, retoot} = CommonAPI.repeat(not_followed_to_followed.id, followed)
2440
2441 params =
2442 %{}
2443 |> Map.put(:type, ["Create", "Announce"])
2444 |> Map.put(:blocking_user, user)
2445 |> Map.put(:muting_user, user)
2446 |> Map.put(:reply_filtering_user, user)
2447 |> Map.put(:reply_visibility, "following")
2448 |> Map.put(:announce_filtering_user, user)
2449 |> Map.put(:user, user)
2450
2451 activities =
2452 [user.ap_id | User.following(user)]
2453 |> ActivityPub.fetch_activities(params)
2454
2455 followed_post_id = followed_post.id
2456 retoot_id = retoot.id
2457
2458 assert [%{id: ^followed_post_id}, %{id: ^retoot_id}] = activities
2459
2460 assert length(activities) == 2
2461 end
2462
2463 # This test is skipped because, while this is the desired behavior,
2464 # there seems to be no good way to achieve it with the method that
2465 # we currently use for detecting to who a reply is directed.
2466 # This is a TODO and should be fixed by a later rewrite of the code
2467 # in question.
2468 @tag skip: true
2469 test "`following` still contains self-replies by friends" do
2470 user = insert(:user)
2471 followed = insert(:user)
2472 not_followed = insert(:user)
2473
2474 User.follow(user, followed)
2475
2476 {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
2477 {:ok, not_followed_post} = CommonAPI.post(not_followed, %{status: "Also hello"})
2478
2479 {:ok, _followed_to_not_followed} =
2480 CommonAPI.post(followed, %{status: "sup", in_reply_to_status_id: not_followed_post.id})
2481
2482 {:ok, _followed_self_reply} =
2483 CommonAPI.post(followed, %{status: "Also cofe", in_reply_to_status_id: followed_post.id})
2484
2485 params =
2486 %{}
2487 |> Map.put(:type, ["Create", "Announce"])
2488 |> Map.put(:blocking_user, user)
2489 |> Map.put(:muting_user, user)
2490 |> Map.put(:reply_filtering_user, user)
2491 |> Map.put(:reply_visibility, "following")
2492 |> Map.put(:announce_filtering_user, user)
2493 |> Map.put(:user, user)
2494
2495 activities =
2496 [user.ap_id | User.following(user)]
2497 |> ActivityPub.fetch_activities(params)
2498
2499 assert length(activities) == 2
2500 end
2501 end
2502
2503 test "allow fetching of accounts with an empty string name field" do
2504 Tesla.Mock.mock(fn
2505 %{method: :get, url: "https://princess.cat/users/mewmew"} ->
2506 file = File.read!("test/fixtures/mewmew_no_name.json")
2507 %Tesla.Env{status: 200, body: file, headers: HttpRequestMock.activitypub_object_headers()}
2508 end)
2509
2510 {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew")
2511 assert user.name == " "
2512 end
2513 end