1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
6 use Oban.Testing, repo: Pleroma.Repo
11 alias Pleroma.Object.Fetcher
12 alias Pleroma.Tests.ObanHelpers
14 alias Pleroma.Web.ActivityPub.Transmogrifier
15 alias Pleroma.Web.AdminAPI.AccountView
16 alias Pleroma.Web.CommonAPI
19 import Pleroma.Factory
20 import ExUnit.CaptureLog
23 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
27 setup do: clear_config([:instance, :max_remote_account_fields])
29 describe "handle_incoming" do
30 test "it works for incoming notices with tag not being an array (kroeg)" do
31 data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
33 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
34 object = Object.normalize(data["object"])
36 assert object.data["emoji"] == %{
37 "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
40 data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
42 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
43 object = Object.normalize(data["object"])
45 assert "test" in object.data["tag"]
48 test "it works for incoming notices with url not being a string (prismo)" do
49 data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
51 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
52 object = Object.normalize(data["object"])
54 assert object.data["url"] == "https://prismo.news/posts/83"
57 test "it cleans up incoming notices which are not really DMs" do
59 other_user = insert(:user)
61 to = [user.ap_id, other_user.ap_id]
64 File.read!("test/fixtures/mastodon-post-activity.json")
74 data = Map.put(data, "object", object)
76 {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
78 assert data["to"] == []
79 assert data["cc"] == to
81 object_data = Object.normalize(activity).data
83 assert object_data["to"] == []
84 assert object_data["cc"] == to
87 test "it ignores an incoming notice if we already have it" do
88 activity = insert(:note_activity)
91 File.read!("test/fixtures/mastodon-post-activity.json")
93 |> Map.put("object", Object.normalize(activity).data)
95 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
97 assert activity == returned_activity
100 @tag capture_log: true
101 test "it fetches reply-to activities if we don't have them" do
103 File.read!("test/fixtures/mastodon-post-activity.json")
108 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
110 data = Map.put(data, "object", object)
111 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
112 returned_object = Object.normalize(returned_activity, false)
115 Activity.get_create_by_object_ap_id(
116 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
119 assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
122 test "it does not fetch reply-to activities beyond max replies depth limit" do
124 File.read!("test/fixtures/mastodon-post-activity.json")
129 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
131 data = Map.put(data, "object", object)
133 with_mock Pleroma.Web.Federator,
134 allowed_thread_distance?: fn _ -> false end do
135 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
137 returned_object = Object.normalize(returned_activity, false)
139 refute Activity.get_create_by_object_ap_id(
140 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
143 assert returned_object.data["inReplyToAtomUri"] ==
144 "https://shitposter.club/notice/2827873"
148 test "it does not crash if the object in inReplyTo can't be fetched" do
150 File.read!("test/fixtures/mastodon-post-activity.json")
155 |> Map.put("inReplyTo", "https://404.site/whatever")
159 |> Map.put("object", object)
161 assert capture_log(fn ->
162 {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
163 end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil"
166 test "it does not work for deactivated users" do
167 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
169 insert(:user, ap_id: data["actor"], deactivated: true)
171 assert {:error, _} = Transmogrifier.handle_incoming(data)
174 test "it works for incoming notices" do
175 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
177 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
180 "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
182 assert data["context"] ==
183 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
185 assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
187 assert data["cc"] == [
188 "http://mastodon.example.org/users/admin/followers",
189 "http://localtesting.pleroma.lol/users/lain"
192 assert data["actor"] == "http://mastodon.example.org/users/admin"
194 object_data = Object.normalize(data["object"]).data
196 assert object_data["id"] ==
197 "http://mastodon.example.org/users/admin/statuses/99512778738411822"
199 assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
201 assert object_data["cc"] == [
202 "http://mastodon.example.org/users/admin/followers",
203 "http://localtesting.pleroma.lol/users/lain"
206 assert object_data["actor"] == "http://mastodon.example.org/users/admin"
207 assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
209 assert object_data["context"] ==
210 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
212 assert object_data["sensitive"] == true
214 user = User.get_cached_by_ap_id(object_data["actor"])
216 assert user.note_count == 1
219 test "it works for incoming notices with hashtags" do
220 data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
222 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
223 object = Object.normalize(data["object"])
225 assert Enum.at(object.data["tag"], 2) == "moo"
228 test "it works for incoming questions" do
229 data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
231 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
233 object = Object.normalize(activity)
235 assert Enum.all?(object.data["oneOf"], fn choice ->
238 "Everyone knows that!",
239 "25 char limit is dumb",
240 "I can't even fit a funny"
245 test "it works for incoming listens" do
247 "@context" => "https://www.w3.org/ns/activitystreams",
248 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
251 "id" => "http://mastodon.example.org/users/admin/listens/1234/activity",
252 "actor" => "http://mastodon.example.org/users/admin",
255 "id" => "http://mastodon.example.org/users/admin/listens/1234",
256 "attributedTo" => "http://mastodon.example.org/users/admin",
257 "title" => "lain radio episode 1",
259 "album" => "lain radio",
264 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
266 object = Object.normalize(activity)
268 assert object.data["title"] == "lain radio episode 1"
269 assert object.data["artist"] == "lain"
270 assert object.data["album"] == "lain radio"
271 assert object.data["length"] == 180_000
274 test "it rewrites Note votes to Answers and increments vote counters on question activities" do
278 CommonAPI.post(user, %{
280 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
283 object = Object.normalize(activity)
286 File.read!("test/fixtures/mastodon-vote.json")
288 |> Kernel.put_in(["to"], user.ap_id)
289 |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
290 |> Kernel.put_in(["object", "to"], user.ap_id)
292 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
293 answer_object = Object.normalize(activity)
294 assert answer_object.data["type"] == "Answer"
295 object = Object.get_by_ap_id(object.data["id"])
298 object.data["oneOf"],
300 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
306 test "it works for incoming notices with contentMap" do
308 File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
310 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
311 object = Object.normalize(data["object"])
313 assert object.data["content"] ==
314 "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
317 test "it works for incoming notices with to/cc not being an array (kroeg)" do
318 data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
320 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
321 object = Object.normalize(data["object"])
323 assert object.data["content"] ==
324 "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
327 test "it ensures that as:Public activities make it to their followers collection" do
331 File.read!("test/fixtures/mastodon-post-activity.json")
333 |> Map.put("actor", user.ap_id)
334 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
339 |> Map.put("attributedTo", user.ap_id)
340 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
342 |> Map.put("id", user.ap_id <> "/activities/12345678")
344 data = Map.put(data, "object", object)
346 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
348 assert data["cc"] == [User.ap_followers(user)]
351 test "it ensures that address fields become lists" do
355 File.read!("test/fixtures/mastodon-post-activity.json")
357 |> Map.put("actor", user.ap_id)
358 |> Map.put("to", nil)
359 |> Map.put("cc", nil)
363 |> Map.put("attributedTo", user.ap_id)
364 |> Map.put("to", nil)
365 |> Map.put("cc", nil)
366 |> Map.put("id", user.ap_id <> "/activities/12345678")
368 data = Map.put(data, "object", object)
370 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
372 assert !is_nil(data["to"])
373 assert !is_nil(data["cc"])
376 test "it strips internal likes" do
378 File.read!("test/fixtures/mastodon-post-activity.json")
383 "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
384 "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
386 "type" => "OrderedCollection"
389 object = Map.put(data["object"], "likes", likes)
390 data = Map.put(data, "object", object)
392 {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
394 refute Map.has_key?(object.data, "likes")
397 test "it strips internal reactions" do
399 {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
400 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "📢")
402 %{object: object} = Activity.get_by_id_with_object(activity.id)
403 assert Map.has_key?(object.data, "reactions")
404 assert Map.has_key?(object.data, "reaction_count")
406 object_data = Transmogrifier.strip_internal_fields(object.data)
407 refute Map.has_key?(object_data, "reactions")
408 refute Map.has_key?(object_data, "reaction_count")
411 test "it works for incomming unfollows with an existing follow" do
415 File.read!("test/fixtures/mastodon-follow-activity.json")
417 |> Map.put("object", user.ap_id)
419 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
422 File.read!("test/fixtures/mastodon-unfollow-activity.json")
424 |> Map.put("object", follow_data)
426 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
428 assert data["type"] == "Undo"
429 assert data["object"]["type"] == "Follow"
430 assert data["object"]["object"] == user.ap_id
431 assert data["actor"] == "http://mastodon.example.org/users/admin"
433 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
436 test "it works for incoming follows to locked account" do
437 pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
438 user = insert(:user, locked: true)
441 File.read!("test/fixtures/mastodon-follow-activity.json")
443 |> Map.put("object", user.ap_id)
445 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
447 assert data["type"] == "Follow"
448 assert data["object"] == user.ap_id
449 assert data["state"] == "pending"
450 assert data["actor"] == "http://mastodon.example.org/users/admin"
452 assert [^pending_follower] = User.get_follow_requests(user)
455 test "it works for incoming accepts which were pre-accepted" do
456 follower = insert(:user)
457 followed = insert(:user)
459 {:ok, follower} = User.follow(follower, followed)
460 assert User.following?(follower, followed) == true
462 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
465 File.read!("test/fixtures/mastodon-accept-activity.json")
467 |> Map.put("actor", followed.ap_id)
470 accept_data["object"]
471 |> Map.put("actor", follower.ap_id)
472 |> Map.put("id", follow_activity.data["id"])
474 accept_data = Map.put(accept_data, "object", object)
476 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
477 refute activity.local
479 assert activity.data["object"] == follow_activity.data["id"]
481 assert activity.data["id"] == accept_data["id"]
483 follower = User.get_cached_by_id(follower.id)
485 assert User.following?(follower, followed) == true
488 test "it works for incoming accepts which were orphaned" do
489 follower = insert(:user)
490 followed = insert(:user, locked: true)
492 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
495 File.read!("test/fixtures/mastodon-accept-activity.json")
497 |> Map.put("actor", followed.ap_id)
500 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
502 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
503 assert activity.data["object"] == follow_activity.data["id"]
505 follower = User.get_cached_by_id(follower.id)
507 assert User.following?(follower, followed) == true
510 test "it works for incoming accepts which are referenced by IRI only" do
511 follower = insert(:user)
512 followed = insert(:user, locked: true)
514 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
517 File.read!("test/fixtures/mastodon-accept-activity.json")
519 |> Map.put("actor", followed.ap_id)
520 |> Map.put("object", follow_activity.data["id"])
522 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
523 assert activity.data["object"] == follow_activity.data["id"]
525 follower = User.get_cached_by_id(follower.id)
527 assert User.following?(follower, followed) == true
529 follower = User.get_by_id(follower.id)
530 assert follower.following_count == 1
532 followed = User.get_by_id(followed.id)
533 assert followed.follower_count == 1
536 test "it fails for incoming accepts which cannot be correlated" do
537 follower = insert(:user)
538 followed = insert(:user, locked: true)
541 File.read!("test/fixtures/mastodon-accept-activity.json")
543 |> Map.put("actor", followed.ap_id)
546 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
548 :error = Transmogrifier.handle_incoming(accept_data)
550 follower = User.get_cached_by_id(follower.id)
552 refute User.following?(follower, followed) == true
555 test "it fails for incoming rejects which cannot be correlated" do
556 follower = insert(:user)
557 followed = insert(:user, locked: true)
560 File.read!("test/fixtures/mastodon-reject-activity.json")
562 |> Map.put("actor", followed.ap_id)
565 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
567 :error = Transmogrifier.handle_incoming(accept_data)
569 follower = User.get_cached_by_id(follower.id)
571 refute User.following?(follower, followed) == true
574 test "it works for incoming rejects which are orphaned" do
575 follower = insert(:user)
576 followed = insert(:user, locked: true)
578 {:ok, follower} = User.follow(follower, followed)
579 {:ok, _, _, _follow_activity} = CommonAPI.follow(follower, followed)
581 assert User.following?(follower, followed) == true
584 File.read!("test/fixtures/mastodon-reject-activity.json")
586 |> Map.put("actor", followed.ap_id)
589 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
591 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
592 refute activity.local
593 assert activity.data["id"] == reject_data["id"]
595 follower = User.get_cached_by_id(follower.id)
597 assert User.following?(follower, followed) == false
600 test "it works for incoming rejects which are referenced by IRI only" do
601 follower = insert(:user)
602 followed = insert(:user, locked: true)
604 {:ok, follower} = User.follow(follower, followed)
605 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
607 assert User.following?(follower, followed) == true
610 File.read!("test/fixtures/mastodon-reject-activity.json")
612 |> Map.put("actor", followed.ap_id)
613 |> Map.put("object", follow_activity.data["id"])
615 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
617 follower = User.get_cached_by_id(follower.id)
619 assert User.following?(follower, followed) == false
622 test "it rejects activities without a valid ID" do
626 File.read!("test/fixtures/mastodon-follow-activity.json")
628 |> Map.put("object", user.ap_id)
631 :error = Transmogrifier.handle_incoming(data)
634 test "skip converting the content when it is nil" do
635 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
637 {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id)
640 Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil}))
642 assert result["content"] == nil
645 test "it converts content of object to html" do
646 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
648 {:ok, %{"content" => content_markdown}} =
649 Fetcher.fetch_and_contain_remote_object_from_id(object_id)
651 {:ok, %Pleroma.Object{data: %{"content" => content}} = object} =
652 Fetcher.fetch_object_from_id(object_id)
654 assert content_markdown ==
655 "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..."
658 "<p>Support this and our other Michigan!/usr/group videos and meetings. Learn more at <a href=\"http://mug.org/membership\">http://mug.org/membership</a></p><p>Twenty Years in Jail: FreeBSD’s Jails, Then and Now</p><p>Jails started as a limited virtualization system, but over the last two years they’ve…</p>"
660 assert object.data["mediaType"] == "text/html"
663 test "it remaps video URLs as attachments if necessary" do
665 Fetcher.fetch_object_from_id(
666 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
669 assert object.data["url"] ==
670 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
672 assert object.data["attachment"] == [
675 "mediaType" => "video/mp4",
679 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
680 "mediaType" => "video/mp4"
687 Fetcher.fetch_object_from_id(
688 "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
691 assert object.data["attachment"] == [
694 "mediaType" => "video/mp4",
698 "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
699 "mediaType" => "video/mp4"
705 assert object.data["url"] ==
706 "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
709 test "it accepts Flag activities" do
711 other_user = insert(:user)
713 {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
714 object = Object.normalize(activity)
718 "id" => activity.data["id"],
719 "content" => "test post",
720 "published" => object.data["published"],
721 "actor" => AccountView.render("show.json", %{user: user, skip_visibility_check: true})
725 "@context" => "https://www.w3.org/ns/activitystreams",
726 "cc" => [user.ap_id],
727 "object" => [user.ap_id, activity.data["id"]],
729 "content" => "blocked AND reported!!!",
730 "actor" => other_user.ap_id
733 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
735 assert activity.data["object"] == [user.ap_id, note_obj]
736 assert activity.data["content"] == "blocked AND reported!!!"
737 assert activity.data["actor"] == other_user.ap_id
738 assert activity.data["cc"] == [user.ap_id]
741 test "it correctly processes messages with non-array to field" do
745 "@context" => "https://www.w3.org/ns/activitystreams",
746 "to" => "https://www.w3.org/ns/activitystreams#Public",
749 "content" => "blah blah blah",
751 "attributedTo" => user.ap_id,
754 "actor" => user.ap_id
757 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
759 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
762 test "it correctly processes messages with non-array cc field" do
766 "@context" => "https://www.w3.org/ns/activitystreams",
767 "to" => user.follower_address,
768 "cc" => "https://www.w3.org/ns/activitystreams#Public",
771 "content" => "blah blah blah",
773 "attributedTo" => user.ap_id,
776 "actor" => user.ap_id
779 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
781 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
782 assert [user.follower_address] == activity.data["to"]
785 test "it correctly processes messages with weirdness in address fields" do
789 "@context" => "https://www.w3.org/ns/activitystreams",
790 "to" => [nil, user.follower_address],
791 "cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]],
796 "attributedTo" => user.ap_id,
799 "actor" => user.ap_id
802 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
804 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
805 assert [user.follower_address] == activity.data["to"]
808 test "it accepts Move activities" do
809 old_user = insert(:user)
810 new_user = insert(:user)
813 "@context" => "https://www.w3.org/ns/activitystreams",
815 "actor" => old_user.ap_id,
816 "object" => old_user.ap_id,
817 "target" => new_user.ap_id
820 assert :error = Transmogrifier.handle_incoming(message)
822 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
824 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
825 assert activity.actor == old_user.ap_id
826 assert activity.data["actor"] == old_user.ap_id
827 assert activity.data["object"] == old_user.ap_id
828 assert activity.data["target"] == new_user.ap_id
829 assert activity.data["type"] == "Move"
833 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
834 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
835 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
839 "test/fixtures/mastodon-post-activity.json"
843 items = get_in(data, ["object", "replies", "first", "items"])
844 assert length(items) > 0
846 %{data: data, items: items}
849 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
853 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
855 {:ok, _activity} = Transmogrifier.handle_incoming(data)
858 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
859 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
863 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
865 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
867 {:ok, _activity} = Transmogrifier.handle_incoming(data)
869 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
873 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
874 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
875 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
880 {:ok, activity} = CommonAPI.post(user, %{status: "post1"})
883 CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
886 CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
888 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
890 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
892 Repo.delete(activity.object)
893 Repo.delete(activity)
895 %{federation_output: federation_output, replies_uris: replies_uris}
898 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
899 federation_output: federation_output,
900 replies_uris: replies_uris
902 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
904 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
906 for id <- replies_uris do
907 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
908 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
912 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
913 %{federation_output: federation_output} do
914 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
916 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
918 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
922 describe "prepare outgoing" do
923 test "it inlines private announced objects" do
926 {:ok, activity} = CommonAPI.post(user, %{status: "hey", visibility: "private"})
928 {:ok, announce_activity} = CommonAPI.repeat(activity.id, user)
930 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
932 assert modified["object"]["content"] == "hey"
933 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
936 test "it turns mentions into tags" do
938 other_user = insert(:user)
941 CommonAPI.post(user, %{status: "hey, @#{other_user.nickname}, how are ya? #2hu"})
943 with_mock Pleroma.Notification,
944 get_notified_from_activity: fn _, _ -> [] end do
945 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
947 object = modified["object"]
949 expected_mention = %{
950 "href" => other_user.ap_id,
951 "name" => "@#{other_user.nickname}",
956 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
961 refute called(Pleroma.Notification.get_notified_from_activity(:_, :_))
962 assert Enum.member?(object["tag"], expected_tag)
963 assert Enum.member?(object["tag"], expected_mention)
967 test "it adds the sensitive property" do
970 {:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"})
971 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
973 assert modified["object"]["sensitive"]
976 test "it adds the json-ld context and the conversation property" do
979 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
980 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
982 assert modified["@context"] ==
983 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
985 assert modified["object"]["conversation"] == modified["context"]
988 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
991 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
992 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
994 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
997 test "it strips internal hashtag data" do
1000 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu"})
1003 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1004 "type" => "Hashtag",
1008 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1010 assert modified["object"]["tag"] == [expected_tag]
1013 test "it strips internal fields" do
1014 user = insert(:user)
1016 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu :firefox:"})
1018 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1020 assert length(modified["object"]["tag"]) == 2
1022 assert is_nil(modified["object"]["emoji"])
1023 assert is_nil(modified["object"]["like_count"])
1024 assert is_nil(modified["object"]["announcements"])
1025 assert is_nil(modified["object"]["announcement_count"])
1026 assert is_nil(modified["object"]["context_id"])
1029 test "it strips internal fields of article" do
1030 activity = insert(:article_activity)
1032 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1034 assert length(modified["object"]["tag"]) == 2
1036 assert is_nil(modified["object"]["emoji"])
1037 assert is_nil(modified["object"]["like_count"])
1038 assert is_nil(modified["object"]["announcements"])
1039 assert is_nil(modified["object"]["announcement_count"])
1040 assert is_nil(modified["object"]["context_id"])
1041 assert is_nil(modified["object"]["likes"])
1044 test "the directMessage flag is present" do
1045 user = insert(:user)
1046 other_user = insert(:user)
1048 {:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"})
1050 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1052 assert modified["directMessage"] == false
1054 {:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"})
1056 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1058 assert modified["directMessage"] == false
1061 CommonAPI.post(user, %{
1062 status: "@#{other_user.nickname} :moominmamma:",
1063 visibility: "direct"
1066 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1068 assert modified["directMessage"] == true
1071 test "it strips BCC field" do
1072 user = insert(:user)
1073 {:ok, list} = Pleroma.List.create("foo", user)
1075 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1077 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1079 assert is_nil(modified["bcc"])
1082 test "it can handle Listen activities" do
1083 listen_activity = insert(:listen)
1085 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1087 assert modified["type"] == "Listen"
1089 user = insert(:user)
1091 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1093 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1097 describe "user upgrade" do
1098 test "it upgrades a user to activitypub" do
1101 nickname: "rye@niu.moe",
1103 ap_id: "https://niu.moe/users/rye",
1104 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1107 user_two = insert(:user)
1108 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
1110 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1111 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
1112 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1114 user = User.get_cached_by_id(user.id)
1115 assert user.note_count == 1
1117 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1118 ObanHelpers.perform_all()
1120 assert user.ap_enabled
1121 assert user.note_count == 1
1122 assert user.follower_address == "https://niu.moe/users/rye/followers"
1123 assert user.following_address == "https://niu.moe/users/rye/following"
1125 user = User.get_cached_by_id(user.id)
1126 assert user.note_count == 1
1128 activity = Activity.get_by_id(activity.id)
1129 assert user.follower_address in activity.recipients
1135 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1144 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1149 refute "..." in activity.recipients
1151 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1152 refute user.follower_address in unrelated_activity.recipients
1154 user_two = User.get_cached_by_id(user_two.id)
1155 assert User.following?(user_two, user)
1156 refute "..." in User.following(user_two)
1160 describe "actor rewriting" do
1161 test "it fixes the actor URL property to be a proper URI" do
1163 "url" => %{"href" => "http://example.com"}
1166 rewritten = Transmogrifier.maybe_fix_user_object(data)
1167 assert rewritten["url"] == "http://example.com"
1171 describe "actor origin containment" do
1172 test "it rejects activities which reference objects with bogus origins" do
1174 "@context" => "https://www.w3.org/ns/activitystreams",
1175 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1176 "actor" => "http://mastodon.example.org/users/admin",
1177 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1178 "object" => "https://info.pleroma.site/activity.json",
1179 "type" => "Announce"
1182 assert capture_log(fn ->
1183 {:error, _} = Transmogrifier.handle_incoming(data)
1184 end) =~ "Object containment failed"
1187 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1189 "@context" => "https://www.w3.org/ns/activitystreams",
1190 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1191 "actor" => "http://mastodon.example.org/users/admin",
1192 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1193 "object" => "https://info.pleroma.site/activity2.json",
1194 "type" => "Announce"
1197 assert capture_log(fn ->
1198 {:error, _} = Transmogrifier.handle_incoming(data)
1199 end) =~ "Object containment failed"
1202 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1204 "@context" => "https://www.w3.org/ns/activitystreams",
1205 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1206 "actor" => "http://mastodon.example.org/users/admin",
1207 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1208 "object" => "https://info.pleroma.site/activity3.json",
1209 "type" => "Announce"
1212 assert capture_log(fn ->
1213 {:error, _} = Transmogrifier.handle_incoming(data)
1214 end) =~ "Object containment failed"
1218 describe "reserialization" do
1219 test "successfully reserializes a message with inReplyTo == nil" do
1220 user = insert(:user)
1223 "@context" => "https://www.w3.org/ns/activitystreams",
1224 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1228 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1233 "attributedTo" => user.ap_id
1235 "actor" => user.ap_id
1238 {:ok, activity} = Transmogrifier.handle_incoming(message)
1240 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1243 test "successfully reserializes a message with AS2 objects in IR" do
1244 user = insert(:user)
1247 "@context" => "https://www.w3.org/ns/activitystreams",
1248 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1252 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1257 "attributedTo" => user.ap_id,
1259 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1260 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1263 "actor" => user.ap_id
1266 {:ok, activity} = Transmogrifier.handle_incoming(message)
1268 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1272 test "Rewrites Answers to Notes" do
1273 user = insert(:user)
1275 {:ok, poll_activity} =
1276 CommonAPI.post(user, %{
1278 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1281 poll_object = Object.normalize(poll_activity)
1282 # TODO: Replace with CommonAPI vote creation when implemented
1284 File.read!("test/fixtures/mastodon-vote.json")
1286 |> Kernel.put_in(["to"], user.ap_id)
1287 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1288 |> Kernel.put_in(["object", "to"], user.ap_id)
1290 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1291 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1293 assert data["object"]["type"] == "Note"
1296 describe "fix_explicit_addressing" do
1298 user = insert(:user)
1302 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1303 explicitly_mentioned_actors = [
1304 "https://pleroma.gold/users/user1",
1305 "https://pleroma.gold/user2"
1309 "actor" => user.ap_id,
1310 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1313 Enum.map(explicitly_mentioned_actors, fn href ->
1314 %{"type" => "Mention", "href" => href}
1318 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1319 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1320 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1321 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1324 test "does not move actor's follower collection to cc", %{user: user} do
1326 "actor" => user.ap_id,
1327 "to" => [user.follower_address],
1331 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1332 assert user.follower_address in fixed_object["to"]
1333 refute user.follower_address in fixed_object["cc"]
1336 test "removes recipient's follower collection from cc", %{user: user} do
1337 recipient = insert(:user)
1340 "actor" => user.ap_id,
1341 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1342 "cc" => [user.follower_address, recipient.follower_address]
1345 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1347 assert user.follower_address in fixed_object["cc"]
1348 refute recipient.follower_address in fixed_object["cc"]
1349 refute recipient.follower_address in fixed_object["to"]
1353 describe "fix_summary/1" do
1354 test "returns fixed object" do
1355 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1356 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1357 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1361 describe "fix_in_reply_to/2" do
1362 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1365 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1369 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1370 assert Transmogrifier.fix_in_reply_to(data) == data
1373 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1374 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1377 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1379 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1380 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1381 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1384 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1386 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1387 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1388 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1391 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1393 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1394 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1395 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1397 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1398 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1399 assert modified_object["inReplyTo"] == []
1400 assert modified_object["inReplyToAtomUri"] == ""
1403 @tag capture_log: true
1404 test "returns modified object when allowed incoming reply", %{data: data} do
1409 "https://shitposter.club/notice/2827873"
1412 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1413 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1415 assert modified_object["inReplyTo"] ==
1416 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1418 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1420 assert modified_object["context"] ==
1421 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1425 describe "fix_url/1" do
1426 test "fixes data for object when url is map" do
1430 "mimeType" => "video/mp4",
1431 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1435 assert Transmogrifier.fix_url(object) == %{
1436 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1440 test "fixes data for video object" do
1446 "mimeType" => "video/mp4",
1447 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1451 "mimeType" => "video/mp4",
1452 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1456 "mimeType" => "text/html",
1457 "href" => "https://peertube.-2d4c2d1630e3"
1461 "mimeType" => "text/html",
1462 "href" => "https://peertube.-2d4c2d16377-42"
1467 assert Transmogrifier.fix_url(object) == %{
1470 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1471 "mimeType" => "video/mp4",
1476 "url" => "https://peertube.-2d4c2d1630e3"
1480 test "fixes url for not Video object" do
1486 "mimeType" => "text/html",
1487 "href" => "https://peertube.-2d4c2d1630e3"
1491 "mimeType" => "text/html",
1492 "href" => "https://peertube.-2d4c2d16377-42"
1497 assert Transmogrifier.fix_url(object) == %{
1499 "url" => "https://peertube.-2d4c2d1630e3"
1502 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1508 test "retunrs not modified object" do
1509 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1513 describe "get_obj_helper/2" do
1514 test "returns nil when cannot normalize object" do
1515 assert capture_log(fn ->
1516 refute Transmogrifier.get_obj_helper("test-obj-id")
1517 end) =~ "Unsupported URI scheme"
1520 @tag capture_log: true
1521 test "returns {:ok, %Object{}} for success case" do
1522 assert {:ok, %Object{}} =
1523 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1527 describe "fix_attachments/1" do
1528 test "returns not modified object" do
1529 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1530 assert Transmogrifier.fix_attachments(data) == data
1533 test "returns modified object when attachment is map" do
1534 assert Transmogrifier.fix_attachments(%{
1536 "mediaType" => "video/mp4",
1537 "url" => "https://peertube.moe/stat-480.mp4"
1542 "mediaType" => "video/mp4",
1544 %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
1551 test "returns modified object when attachment is list" do
1552 assert Transmogrifier.fix_attachments(%{
1554 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1555 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1560 "mediaType" => "video/mp4",
1562 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1566 "mediaType" => "video/mp4",
1568 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1576 describe "fix_emoji/1" do
1577 test "returns not modified object when object not contains tags" do
1578 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1579 assert Transmogrifier.fix_emoji(data) == data
1582 test "returns object with emoji when object contains list tags" do
1583 assert Transmogrifier.fix_emoji(%{
1585 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1586 %{"type" => "Hashtag"}
1589 "emoji" => %{"bib" => "/test"},
1591 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1592 %{"type" => "Hashtag"}
1597 test "returns object with emoji when object contains map tag" do
1598 assert Transmogrifier.fix_emoji(%{
1599 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1601 "emoji" => %{"bib" => "/test"},
1602 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1607 describe "set_replies/1" do
1608 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1610 test "returns unmodified object if activity doesn't have self-replies" do
1611 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1612 assert Transmogrifier.set_replies(data) == data
1615 test "sets `replies` collection with a limited number of self-replies" do
1616 [user, another_user] = insert_list(2, :user)
1618 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
1620 {:ok, %{id: id2} = self_reply1} =
1621 CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1})
1623 {:ok, self_reply2} =
1624 CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1})
1626 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1627 {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1})
1630 CommonAPI.post(user, %{
1631 status: "self-reply to self-reply",
1632 in_reply_to_status_id: id2
1636 CommonAPI.post(another_user, %{
1637 status: "another user's reply",
1638 in_reply_to_status_id: id1
1641 object = Object.normalize(activity)
1642 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
1644 assert %{"type" => "Collection", "items" => ^replies_uris} =
1645 Transmogrifier.set_replies(object.data)["replies"]
1649 test "take_emoji_tags/1" do
1650 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
1652 assert Transmogrifier.take_emoji_tags(user) == [
1654 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
1655 "id" => "https://example.org/firefox.png",
1656 "name" => ":firefox:",
1658 "updated" => "1970-01-01T00:00:00Z"