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