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