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