aboutsummaryrefslogtreecommitdiff
path: root/test/pleroma/web/activity_pub/transmogrifier
diff options
context:
space:
mode:
Diffstat (limited to 'test/pleroma/web/activity_pub/transmogrifier')
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs91
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs182
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs153
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs79
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/article_handling_test.exs82
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs87
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs63
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/chat_message_test.exs171
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs115
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs92
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs42
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs208
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs78
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs748
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs36
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs173
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs67
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs185
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs159
-rw-r--r--test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs122
20 files changed, 2933 insertions, 0 deletions
diff --git a/test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs
new file mode 100644
index 0000000..968fd4e
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs
@@ -0,0 +1,91 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.AcceptHandlingTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ test "it works for incoming accepts which were pre-accepted" do
+ follower = insert(:user)
+ followed = insert(:user)
+
+ {:ok, follower, followed} = User.follow(follower, followed)
+ assert User.following?(follower, followed) == true
+
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
+
+ accept_data =
+ File.read!("test/fixtures/mastodon-accept-activity.json")
+ |> Jason.decode!()
+ |> Map.put("actor", followed.ap_id)
+
+ object =
+ accept_data["object"]
+ |> Map.put("actor", follower.ap_id)
+ |> Map.put("id", follow_activity.data["id"])
+
+ accept_data = Map.put(accept_data, "object", object)
+
+ {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
+ refute activity.local
+
+ assert activity.data["object"] == follow_activity.data["id"]
+
+ assert activity.data["id"] == accept_data["id"]
+
+ follower = User.get_cached_by_id(follower.id)
+
+ assert User.following?(follower, followed) == true
+ end
+
+ test "it works for incoming accepts which are referenced by IRI only" do
+ follower = insert(:user)
+ followed = insert(:user, is_locked: true)
+
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
+
+ accept_data =
+ File.read!("test/fixtures/mastodon-accept-activity.json")
+ |> Jason.decode!()
+ |> Map.put("actor", followed.ap_id)
+ |> Map.put("object", follow_activity.data["id"])
+
+ {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
+ assert activity.data["object"] == follow_activity.data["id"]
+
+ follower = User.get_cached_by_id(follower.id)
+
+ assert User.following?(follower, followed) == true
+
+ follower = User.get_by_id(follower.id)
+ assert follower.following_count == 1
+
+ followed = User.get_by_id(followed.id)
+ assert followed.follower_count == 1
+ end
+
+ test "it fails for incoming accepts which cannot be correlated" do
+ follower = insert(:user)
+ followed = insert(:user, is_locked: true)
+
+ accept_data =
+ File.read!("test/fixtures/mastodon-accept-activity.json")
+ |> Jason.decode!()
+ |> Map.put("actor", followed.ap_id)
+
+ accept_data =
+ Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
+
+ {:error, _} = Transmogrifier.handle_incoming(accept_data)
+
+ follower = User.get_cached_by_id(follower.id)
+
+ refute User.following?(follower, followed) == true
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs
new file mode 100644
index 0000000..a8a7edc
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs
@@ -0,0 +1,182 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.AddRemoveHandlingTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase, async: true
+
+ require Pleroma.Constants
+
+ import Pleroma.Factory
+
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ test "it accepts Add/Remove activities" do
+ user =
+ "test/fixtures/users_mock/user.json"
+ |> File.read!()
+ |> String.replace("{{nickname}}", "lain")
+
+ object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
+
+ object =
+ "test/fixtures/statuses/note.json"
+ |> File.read!()
+ |> String.replace("{{nickname}}", "lain")
+ |> String.replace("{{object_id}}", object_id)
+
+ object_url = "https://example.com/objects/#{object_id}"
+
+ actor = "https://example.com/users/lain"
+
+ Tesla.Mock.mock(fn
+ %{
+ method: :get,
+ url: ^actor
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: user,
+ headers: [{"content-type", "application/activity+json"}]
+ }
+
+ %{
+ method: :get,
+ url: ^object_url
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: object,
+ headers: [{"content-type", "application/activity+json"}]
+ }
+
+ %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
+ %Tesla.Env{
+ status: 200,
+ body:
+ "test/fixtures/users_mock/masto_featured.json"
+ |> File.read!()
+ |> String.replace("{{domain}}", "example.com")
+ |> String.replace("{{nickname}}", "lain"),
+ headers: [{"content-type", "application/activity+json"}]
+ }
+ end)
+
+ message = %{
+ "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
+ "actor" => actor,
+ "object" => object_url,
+ "target" => "https://example.com/users/lain/collections/featured",
+ "type" => "Add",
+ "to" => [Pleroma.Constants.as_public()],
+ "cc" => ["https://example.com/users/lain/followers"],
+ "bcc" => [],
+ "bto" => []
+ }
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+ assert activity.data == message
+ user = User.get_cached_by_ap_id(actor)
+ assert user.pinned_objects[object_url]
+
+ remove = %{
+ "id" => "http://localhost:400/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
+ "actor" => actor,
+ "object" => object_url,
+ "target" => "https://example.com/users/lain/collections/featured",
+ "type" => "Remove",
+ "to" => [Pleroma.Constants.as_public()],
+ "cc" => ["https://example.com/users/lain/followers"],
+ "bcc" => [],
+ "bto" => []
+ }
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(remove)
+ assert activity.data == remove
+
+ user = refresh_record(user)
+ refute user.pinned_objects[object_url]
+ end
+
+ test "Add/Remove activities for remote users without featured address" do
+ user = insert(:user, local: false, domain: "example.com")
+
+ user =
+ user
+ |> Ecto.Changeset.change(featured_address: nil)
+ |> Repo.update!()
+
+ %{host: host} = URI.parse(user.ap_id)
+
+ user_data =
+ "test/fixtures/users_mock/user.json"
+ |> File.read!()
+ |> String.replace("{{nickname}}", user.nickname)
+
+ object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
+
+ object =
+ "test/fixtures/statuses/note.json"
+ |> File.read!()
+ |> String.replace("{{nickname}}", user.nickname)
+ |> String.replace("{{object_id}}", object_id)
+
+ object_url = "https://#{host}/objects/#{object_id}"
+
+ actor = "https://#{host}/users/#{user.nickname}"
+
+ featured = "https://#{host}/users/#{user.nickname}/collections/featured"
+
+ Tesla.Mock.mock(fn
+ %{
+ method: :get,
+ url: ^actor
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: user_data,
+ headers: [{"content-type", "application/activity+json"}]
+ }
+
+ %{
+ method: :get,
+ url: ^object_url
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: object,
+ headers: [{"content-type", "application/activity+json"}]
+ }
+
+ %{method: :get, url: ^featured} ->
+ %Tesla.Env{
+ status: 200,
+ body:
+ "test/fixtures/users_mock/masto_featured.json"
+ |> File.read!()
+ |> String.replace("{{domain}}", "#{host}")
+ |> String.replace("{{nickname}}", user.nickname),
+ headers: [{"content-type", "application/activity+json"}]
+ }
+ end)
+
+ message = %{
+ "id" => "https://#{host}/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
+ "actor" => actor,
+ "object" => object_url,
+ "target" => "https://#{host}/users/#{user.nickname}/collections/featured",
+ "type" => "Add",
+ "to" => [Pleroma.Constants.as_public()],
+ "cc" => ["https://#{host}/users/#{user.nickname}/followers"],
+ "bcc" => [],
+ "bto" => []
+ }
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+ assert activity.data == message
+ user = User.get_cached_by_ap_id(actor)
+ assert user.pinned_objects[object_url]
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs
new file mode 100644
index 0000000..9521cc0
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs
@@ -0,0 +1,153 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnnounceHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ test "it works for incoming honk announces" do
+ user = insert(:user, ap_id: "https://honktest/u/test", local: false)
+ other_user = insert(:user)
+ {:ok, post} = CommonAPI.post(other_user, %{status: "bonkeronk"})
+
+ announce = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "actor" => "https://honktest/u/test",
+ "id" => "https://honktest/u/test/bonk/1793M7B9MQ48847vdx",
+ "object" => post.data["object"],
+ "published" => "2019-06-25T19:33:58Z",
+ "to" => "https://www.w3.org/ns/activitystreams#Public",
+ "type" => "Announce"
+ }
+
+ {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(announce)
+
+ object = Object.get_by_ap_id(post.data["object"])
+
+ assert length(object.data["announcements"]) == 1
+ assert user.ap_id in object.data["announcements"]
+ end
+
+ test "it works for incoming announces with actor being inlined (kroeg)" do
+ data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Jason.decode!()
+
+ _user = insert(:user, local: false, ap_id: data["actor"]["id"])
+ other_user = insert(:user)
+
+ {:ok, post} = CommonAPI.post(other_user, %{status: "kroegeroeg"})
+
+ data =
+ data
+ |> put_in(["object", "id"], post.data["object"])
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == "https://puckipedia.com/"
+ end
+
+ test "it works for incoming announces, fetching the announced object" do
+ data =
+ File.read!("test/fixtures/mastodon-announce.json")
+ |> Jason.decode!()
+ |> Map.put("object", "http://mastodon.example.org/users/admin/statuses/99541947525187367")
+
+ Tesla.Mock.mock(fn
+ %{method: :get} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/mastodon-note-object.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+ end)
+
+ _user = insert(:user, local: false, ap_id: data["actor"])
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+ assert data["type"] == "Announce"
+
+ assert data["id"] ==
+ "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
+
+ assert data["object"] ==
+ "http://mastodon.example.org/users/admin/statuses/99541947525187367"
+
+ assert(Activity.get_create_by_object_ap_id(data["object"]))
+ end
+
+ @tag capture_log: true
+ test "it works for incoming announces with an existing activity" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
+
+ data =
+ File.read!("test/fixtures/mastodon-announce.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+
+ _user = insert(:user, local: false, ap_id: data["actor"])
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+ assert data["type"] == "Announce"
+
+ assert data["id"] ==
+ "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
+
+ assert data["object"] == activity.data["object"]
+
+ assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
+ end
+
+ # Ignore inlined activities for now
+ @tag skip: true
+ test "it works for incoming announces with an inlined activity" do
+ data =
+ File.read!("test/fixtures/mastodon-announce-private.json")
+ |> Jason.decode!()
+
+ _user =
+ insert(:user,
+ local: false,
+ ap_id: data["actor"],
+ follower_address: data["actor"] <> "/followers"
+ )
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+ assert data["type"] == "Announce"
+
+ assert data["id"] ==
+ "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
+
+ object = Object.normalize(data["object"], fetch: false)
+
+ assert object.data["id"] == "http://mastodon.example.org/@admin/99541947525187368"
+ assert object.data["content"] == "this is a private toot"
+ end
+
+ @tag capture_log: true
+ test "it rejects incoming announces with an inlined activity from another origin" do
+ Tesla.Mock.mock(fn
+ %{method: :get} -> %Tesla.Env{status: 404, body: ""}
+ end)
+
+ data =
+ File.read!("test/fixtures/bogus-mastodon-announce.json")
+ |> Jason.decode!()
+
+ _user = insert(:user, local: false, ap_id: data["actor"])
+
+ assert {:error, _e} = Transmogrifier.handle_incoming(data)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs
new file mode 100644
index 0000000..39a1598
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs
@@ -0,0 +1,79 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnswerHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ test "incoming, rewrites Note to Answer and increments vote counters" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ status: "suya...",
+ poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
+ })
+
+ object = Object.normalize(activity, fetch: false)
+ assert object.data["repliesCount"] == nil
+
+ data =
+ File.read!("test/fixtures/mastodon-vote.json")
+ |> Jason.decode!()
+ |> Kernel.put_in(["to"], user.ap_id)
+ |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
+ |> Kernel.put_in(["object", "to"], user.ap_id)
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+ answer_object = Object.normalize(activity, fetch: false)
+ assert answer_object.data["type"] == "Answer"
+ assert answer_object.data["inReplyTo"] == object.data["id"]
+
+ new_object = Object.get_by_ap_id(object.data["id"])
+ assert new_object.data["repliesCount"] == nil
+
+ assert Enum.any?(
+ new_object.data["oneOf"],
+ fn
+ %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
+ _ -> false
+ end
+ )
+ end
+
+ test "outgoing, rewrites Answer to Note" do
+ user = insert(:user)
+
+ {:ok, poll_activity} =
+ CommonAPI.post(user, %{
+ status: "suya...",
+ poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
+ })
+
+ poll_object = Object.normalize(poll_activity, fetch: false)
+ # TODO: Replace with CommonAPI vote creation when implemented
+ data =
+ File.read!("test/fixtures/mastodon-vote.json")
+ |> Jason.decode!()
+ |> Kernel.put_in(["to"], user.ap_id)
+ |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
+ |> Kernel.put_in(["object", "to"], user.ap_id)
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+ {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
+
+ assert data["object"]["type"] == "Note"
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/article_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/article_handling_test.exs
new file mode 100644
index 0000000..2524920
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/article_handling_test.exs
@@ -0,0 +1,82 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.ArticleHandlingTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Object.Fetcher
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ test "Pterotype (Wordpress Plugin) Article" do
+ Tesla.Mock.mock(fn %{url: "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog"} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/wedistribute-user.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+ end)
+
+ data =
+ File.read!("test/fixtures/tesla_mock/wedistribute-create-article.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ object = Object.normalize(data["object"], fetch: false)
+
+ assert object.data["name"] == "The end is near: Mastodon plans to drop OStatus support"
+
+ assert object.data["summary"] ==
+ "One of the largest platforms in the federated social web is dropping the protocol that it started with."
+
+ assert object.data["url"] == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
+ end
+
+ test "Plume Article" do
+ Tesla.Mock.mock(fn
+ %{url: "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+
+ %{url: "https://baptiste.gelez.xyz/@/BaptisteGelez"} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+ end)
+
+ {:ok, object} =
+ Fetcher.fetch_object_from_id(
+ "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"
+ )
+
+ assert object.data["name"] == "This Month in Plume: June 2018"
+
+ assert object.data["url"] ==
+ "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"
+ end
+
+ test "Prismo Article" do
+ Tesla.Mock.mock(fn %{url: "https://prismo.news/@mxb"} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___prismo.news__mxb.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+ end)
+
+ data = File.read!("test/fixtures/prismo-url-map.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"], fetch: false)
+
+ assert object.data["url"] == "https://prismo.news/posts/83"
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs
new file mode 100644
index 0000000..d1eb21a
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs
@@ -0,0 +1,87 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.AudioHandlingTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Pleroma.Factory
+
+ test "it works for incoming listens" do
+ _user = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
+
+ data = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Listen",
+ "id" => "http://mastodon.example.org/users/admin/listens/1234/activity",
+ "actor" => "http://mastodon.example.org/users/admin",
+ "object" => %{
+ "type" => "Audio",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "id" => "http://mastodon.example.org/users/admin/listens/1234",
+ "attributedTo" => "http://mastodon.example.org/users/admin",
+ "title" => "lain radio episode 1",
+ "artist" => "lain",
+ "album" => "lain radio",
+ "length" => 180_000
+ }
+ }
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ object = Object.normalize(activity, fetch: false)
+
+ assert object.data["title"] == "lain radio episode 1"
+ assert object.data["artist"] == "lain"
+ assert object.data["album"] == "lain radio"
+ assert object.data["length"] == 180_000
+ end
+
+ test "Funkwhale Audio object" do
+ Tesla.Mock.mock(fn
+ %{url: "https://channels.tests.funkwhale.audio/federation/actors/compositions"} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/funkwhale_channel.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+ end)
+
+ data = File.read!("test/fixtures/tesla_mock/funkwhale_create_audio.json") |> Jason.decode!()
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ assert object = Object.normalize(activity, fetch: false)
+
+ assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+
+ assert object.data["cc"] == [
+ "https://channels.tests.funkwhale.audio/federation/actors/compositions/followers"
+ ]
+
+ assert object.data["url"] == "https://channels.tests.funkwhale.audio/library/tracks/74"
+
+ assert object.data["attachment"] == [
+ %{
+ "mediaType" => "audio/ogg",
+ "type" => "Link",
+ "url" => [
+ %{
+ "href" =>
+ "https://channels.tests.funkwhale.audio/api/v1/listen/3901e5d8-0445-49d5-9711-e096cf32e515/?upload=42342395-0208-4fee-a38d-259a6dae0871&download=false",
+ "mediaType" => "audio/ogg",
+ "type" => "Link"
+ }
+ ]
+ }
+ ]
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs
new file mode 100644
index 0000000..c9a20b1
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.BlockHandlingTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Activity
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Pleroma.Factory
+
+ test "it works for incoming blocks" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-block-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+
+ blocker = insert(:user, ap_id: data["actor"])
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["type"] == "Block"
+ assert data["object"] == user.ap_id
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+
+ assert User.blocks?(blocker, user)
+ end
+
+ test "incoming blocks successfully tear down any follow relationship" do
+ blocker = insert(:user)
+ blocked = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-block-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", blocked.ap_id)
+ |> Map.put("actor", blocker.ap_id)
+
+ {:ok, blocker, blocked} = User.follow(blocker, blocked)
+ {:ok, blocked, blocker} = User.follow(blocked, blocker)
+
+ assert User.following?(blocker, blocked)
+ assert User.following?(blocked, blocker)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["type"] == "Block"
+ assert data["object"] == blocked.ap_id
+ assert data["actor"] == blocker.ap_id
+
+ blocker = User.get_cached_by_ap_id(data["actor"])
+ blocked = User.get_cached_by_ap_id(data["object"])
+
+ assert User.blocks?(blocker, blocked)
+
+ refute User.following?(blocker, blocked)
+ refute User.following?(blocked, blocker)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/chat_message_test.exs b/test/pleroma/web/activity_pub/transmogrifier/chat_message_test.exs
new file mode 100644
index 0000000..c798a0f
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/chat_message_test.exs
@@ -0,0 +1,171 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.ChatMessageTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Activity
+ alias Pleroma.Chat
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ describe "handle_incoming" do
+ test "handles chonks with attachment" do
+ data = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "actor" => "https://honk.tedunangst.com/u/tedu",
+ "id" => "https://honk.tedunangst.com/u/tedu/honk/x6gt8X8PcyGkQcXxzg1T",
+ "object" => %{
+ "attachment" => [
+ %{
+ "mediaType" => "image/jpeg",
+ "name" => "298p3RG7j27tfsZ9RQ.jpg",
+ "summary" => "298p3RG7j27tfsZ9RQ.jpg",
+ "type" => "Document",
+ "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
+ }
+ ],
+ "attributedTo" => "https://honk.tedunangst.com/u/tedu",
+ "content" => "",
+ "id" => "https://honk.tedunangst.com/u/tedu/chonk/26L4wl5yCbn4dr4y1b",
+ "published" => "2020-05-18T01:13:03Z",
+ "to" => [
+ "https://dontbulling.me/users/lain"
+ ],
+ "type" => "ChatMessage"
+ },
+ "published" => "2020-05-18T01:13:03Z",
+ "to" => [
+ "https://dontbulling.me/users/lain"
+ ],
+ "type" => "Create"
+ }
+
+ _user = insert(:user, ap_id: data["actor"])
+ _user = insert(:user, ap_id: hd(data["to"]))
+
+ assert {:ok, _activity} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it rejects messages that don't contain content" do
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Jason.decode!()
+
+ object =
+ data["object"]
+ |> Map.delete("content")
+
+ data =
+ data
+ |> Map.put("object", object)
+
+ _author =
+ insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now())
+
+ _recipient =
+ insert(:user,
+ ap_id: List.first(data["to"]),
+ local: true,
+ last_refreshed_at: DateTime.utc_now()
+ )
+
+ {:error, _} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it rejects messages that don't concern local users" do
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Jason.decode!()
+
+ _author =
+ insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now())
+
+ _recipient =
+ insert(:user,
+ ap_id: List.first(data["to"]),
+ local: false,
+ last_refreshed_at: DateTime.utc_now()
+ )
+
+ {:error, _} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it rejects messages where the `to` field of activity and object don't match" do
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Jason.decode!()
+
+ author = insert(:user, ap_id: data["actor"])
+ _recipient = insert(:user, ap_id: List.first(data["to"]))
+
+ data =
+ data
+ |> Map.put("to", author.ap_id)
+
+ assert match?({:error, _}, Transmogrifier.handle_incoming(data))
+ refute Object.get_by_ap_id(data["object"]["id"])
+ end
+
+ test "it fetches the actor if they aren't in our system" do
+ Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Jason.decode!()
+ |> Map.put("actor", "http://mastodon.example.org/users/admin")
+ |> put_in(["object", "actor"], "http://mastodon.example.org/users/admin")
+
+ _recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
+
+ {:ok, %Activity{} = _activity} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it doesn't work for deactivated users" do
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Jason.decode!()
+
+ _author =
+ insert(:user,
+ ap_id: data["actor"],
+ local: false,
+ last_refreshed_at: DateTime.utc_now(),
+ is_active: false
+ )
+
+ _recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
+
+ assert {:error, _} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it inserts it and creates a chat" do
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Jason.decode!()
+
+ author =
+ insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now())
+
+ recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
+
+ {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)
+ assert activity.local == false
+
+ assert activity.actor == author.ap_id
+ assert activity.recipients == [recipient.ap_id, author.ap_id]
+
+ %Object{} = object = Object.get_by_ap_id(activity.data["object"])
+
+ assert object
+ assert object.data["content"] == "You expected a cute girl? Too bad. alert(&#39;XSS&#39;)"
+ assert match?(%{"firefox" => _}, object.data["emoji"])
+
+ refute Chat.get(author.id, recipient.ap_id)
+ assert Chat.get(recipient.id, author.ap_id)
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs
new file mode 100644
index 0000000..4a7ff51
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs
@@ -0,0 +1,115 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.DeleteHandlingTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Tests.ObanHelpers
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Pleroma.Factory
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ test "it works for incoming deletes" do
+ activity = insert(:note_activity)
+ deleting_user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-delete.json")
+ |> Jason.decode!()
+ |> Map.put("actor", deleting_user.ap_id)
+ |> put_in(["object", "id"], activity.data["object"])
+
+ {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} =
+ Transmogrifier.handle_incoming(data)
+
+ assert id == data["id"]
+
+ # We delete the Create activity because we base our timelines on it.
+ # This should be changed after we unify objects and activities
+ refute Activity.get_by_id(activity.id)
+ assert actor == deleting_user.ap_id
+
+ # Objects are replaced by a tombstone object.
+ object = Object.normalize(activity.data["object"], fetch: false)
+ assert object.data["type"] == "Tombstone"
+ end
+
+ test "it works for incoming when the object has been pruned" do
+ activity = insert(:note_activity)
+
+ {:ok, object} =
+ Object.normalize(activity.data["object"], fetch: false)
+ |> Repo.delete()
+
+ # TODO: mock cachex
+ Cachex.del(:object_cache, "object:#{object.data["id"]}")
+
+ deleting_user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-delete.json")
+ |> Jason.decode!()
+ |> Map.put("actor", deleting_user.ap_id)
+ |> put_in(["object", "id"], activity.data["object"])
+
+ {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} =
+ Transmogrifier.handle_incoming(data)
+
+ assert id == data["id"]
+
+ # We delete the Create activity because we base our timelines on it.
+ # This should be changed after we unify objects and activities
+ refute Activity.get_by_id(activity.id)
+ assert actor == deleting_user.ap_id
+ end
+
+ test "it fails for incoming deletes with spoofed origin" do
+ activity = insert(:note_activity)
+ %{ap_id: ap_id} = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
+
+ data =
+ File.read!("test/fixtures/mastodon-delete.json")
+ |> Jason.decode!()
+ |> Map.put("actor", ap_id)
+ |> put_in(["object", "id"], activity.data["object"])
+
+ assert match?({:error, _}, Transmogrifier.handle_incoming(data))
+ end
+
+ @tag capture_log: true
+ test "it works for incoming user deletes" do
+ %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
+
+ data =
+ File.read!("test/fixtures/mastodon-delete-user.json")
+ |> Jason.decode!()
+
+ {:ok, _} = Transmogrifier.handle_incoming(data)
+ ObanHelpers.perform_all()
+
+ refute User.get_cached_by_ap_id(ap_id).is_active
+ end
+
+ test "it fails for incoming user deletes with spoofed origin" do
+ %{ap_id: ap_id} = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-delete-user.json")
+ |> Jason.decode!()
+ |> Map.put("actor", ap_id)
+
+ assert match?({:error, _}, Transmogrifier.handle_incoming(data))
+
+ assert User.get_cached_by_ap_id(ap_id)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs
new file mode 100644
index 0000000..83bf59c
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs
@@ -0,0 +1,92 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ test "it works for incoming emoji reactions" do
+ user = insert(:user)
+ other_user = insert(:user, local: false)
+ {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
+
+ data =
+ File.read!("test/fixtures/emoji-reaction.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+ |> Map.put("actor", other_user.ap_id)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == other_user.ap_id
+ assert data["type"] == "EmojiReact"
+ assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
+ assert data["object"] == activity.data["object"]
+ assert data["content"] == "👌"
+
+ object = Object.get_by_ap_id(data["object"])
+
+ assert object.data["reaction_count"] == 1
+ assert match?([["👌", _]], object.data["reactions"])
+ end
+
+ test "it works for incoming unqualified emoji reactions" do
+ user = insert(:user)
+ other_user = insert(:user, local: false)
+ {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
+
+ # woman detective emoji, unqualified
+ unqualified_emoji = [0x1F575, 0x200D, 0x2640] |> List.to_string()
+
+ data =
+ File.read!("test/fixtures/emoji-reaction.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+ |> Map.put("actor", other_user.ap_id)
+ |> Map.put("content", unqualified_emoji)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == other_user.ap_id
+ assert data["type"] == "EmojiReact"
+ assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
+ assert data["object"] == activity.data["object"]
+ # woman detective emoji, fully qualified
+ emoji = [0x1F575, 0xFE0F, 0x200D, 0x2640, 0xFE0F] |> List.to_string()
+ assert data["content"] == emoji
+
+ object = Object.get_by_ap_id(data["object"])
+
+ assert object.data["reaction_count"] == 1
+ assert match?([[^emoji, _]], object.data["reactions"])
+ end
+
+ test "it reject invalid emoji reactions" do
+ user = insert(:user)
+ other_user = insert(:user, local: false)
+ {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
+
+ data =
+ File.read!("test/fixtures/emoji-reaction-too-long.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+ |> Map.put("actor", other_user.ap_id)
+
+ assert {:error, _} = Transmogrifier.handle_incoming(data)
+
+ data =
+ File.read!("test/fixtures/emoji-reaction-no-emoji.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+ |> Map.put("actor", other_user.ap_id)
+
+ assert {:error, _} = Transmogrifier.handle_incoming(data)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs
new file mode 100644
index 0000000..6372aa8
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.EventHandlingTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase
+
+ alias Pleroma.Object.Fetcher
+
+ test "Mobilizon Event object" do
+ Tesla.Mock.mock(fn
+ %{url: "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mobilizon.org-event.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+
+ %{url: "https://mobilizon.org/@tcit"} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mobilizon.org-user.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+ end)
+
+ assert {:ok, object} =
+ Fetcher.fetch_object_from_id(
+ "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
+ )
+
+ assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+ assert object.data["cc"] == ["https://mobilizon.org/@tcit/followers"]
+
+ assert object.data["url"] ==
+ "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
+
+ assert object.data["published"] == "2019-12-17T11:33:56Z"
+ assert object.data["name"] == "Mobilizon Launching Party"
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs
new file mode 100644
index 0000000..3c07926
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs
@@ -0,0 +1,208 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
+ use Pleroma.DataCase
+ alias Pleroma.Activity
+ alias Pleroma.Notification
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.ActivityPub.Utils
+
+ import Pleroma.Factory
+ import Ecto.Query
+ import Mock
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ describe "handle_incoming" do
+ setup do: clear_config([:user, :deny_follow_blocked])
+
+ test "it works for osada follow request" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/osada-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+
+ {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == "https://apfed.club/channel/indio"
+ assert data["type"] == "Follow"
+ assert data["id"] == "https://apfed.club/follow/9"
+
+ activity = Repo.get(Activity, activity.id)
+ assert activity.data["state"] == "accept"
+ assert User.following?(User.get_cached_by_ap_id(data["actor"]), user)
+ end
+
+ test "it works for incoming follow requests" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+
+ {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+ assert data["type"] == "Follow"
+ assert data["id"] == "http://mastodon.example.org/users/admin#follows/2"
+
+ activity = Repo.get(Activity, activity.id)
+ assert activity.data["state"] == "accept"
+ assert User.following?(User.get_cached_by_ap_id(data["actor"]), user)
+
+ [notification] = Notification.for_user(user)
+ assert notification.type == "follow"
+ end
+
+ test "with locked accounts, it does create a Follow, but not an Accept" do
+ user = insert(:user, is_locked: true)
+
+ data =
+ File.read!("test/fixtures/mastodon-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["state"] == "pending"
+
+ refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
+
+ accepts =
+ from(
+ a in Activity,
+ where: fragment("?->>'type' = ?", a.data, "Accept")
+ )
+ |> Repo.all()
+
+ assert Enum.empty?(accepts)
+
+ [notification] = Notification.for_user(user)
+ assert notification.type == "follow_request"
+ end
+
+ test "it works for follow requests when you are already followed, creating a new accept activity" do
+ # This is important because the remote might have the wrong idea about the
+ # current follow status. This can lead to instance A thinking that x@A is
+ # followed by y@B, but B thinks they are not. In this case, the follow can
+ # never go through again because it will never get an Accept.
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+
+ {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
+
+ accepts =
+ from(
+ a in Activity,
+ where: fragment("?->>'type' = ?", a.data, "Accept")
+ )
+ |> Repo.all()
+
+ assert length(accepts) == 1
+
+ data =
+ File.read!("test/fixtures/mastodon-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("id", String.replace(data["id"], "2", "3"))
+ |> Map.put("object", user.ap_id)
+
+ {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
+
+ accepts =
+ from(
+ a in Activity,
+ where: fragment("?->>'type' = ?", a.data, "Accept")
+ )
+ |> Repo.all()
+
+ assert length(accepts) == 2
+ end
+
+ test "it rejects incoming follow requests from blocked users when deny_follow_blocked is enabled" do
+ clear_config([:user, :deny_follow_blocked], true)
+
+ user = insert(:user)
+ {:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin")
+
+ {:ok, _user_relationship} = User.block(user, target)
+
+ data =
+ File.read!("test/fixtures/mastodon-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+
+ {:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data)
+
+ %Activity{} = activity = Activity.get_by_ap_id(id)
+
+ assert activity.data["state"] == "reject"
+ end
+
+ test "it rejects incoming follow requests if the following errors for some reason" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+
+ with_mock Pleroma.User, [:passthrough], follow: fn _, _, _ -> {:error, :testing} end do
+ {:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data)
+
+ %Activity{} = activity = Activity.get_by_ap_id(id)
+
+ assert activity.data["state"] == "reject"
+ end
+ end
+
+ test "it works for incoming follow requests from hubzilla" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/hubzilla-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+ |> Utils.normalize_params()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == "https://hubzilla.example.org/channel/kaniini"
+ assert data["type"] == "Follow"
+ assert data["id"] == "https://hubzilla.example.org/channel/kaniini#follows/2"
+ assert User.following?(User.get_cached_by_ap_id(data["actor"]), user)
+ end
+
+ test "it works for incoming follows to locked account" do
+ pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
+ user = insert(:user, is_locked: true)
+
+ data =
+ File.read!("test/fixtures/mastodon-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["type"] == "Follow"
+ assert data["object"] == user.ap_id
+ assert data["state"] == "pending"
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+
+ assert [^pending_follower] = User.get_follow_requests(user)
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs
new file mode 100644
index 0000000..c02f66d
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs
@@ -0,0 +1,78 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Activity
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ test "it works for incoming likes" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
+
+ data =
+ File.read!("test/fixtures/mastodon-like.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+
+ _actor = insert(:user, ap_id: data["actor"], local: false)
+
+ {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ refute Enum.empty?(activity.recipients)
+
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+ assert data["type"] == "Like"
+ assert data["id"] == "http://mastodon.example.org/users/admin#likes/2"
+ assert data["object"] == activity.data["object"]
+ end
+
+ test "it works for incoming misskey likes, turning them into EmojiReacts" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
+
+ data =
+ File.read!("test/fixtures/misskey-like.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+
+ _actor = insert(:user, ap_id: data["actor"], local: false)
+
+ {:ok, %Activity{data: activity_data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert activity_data["actor"] == data["actor"]
+ assert activity_data["type"] == "EmojiReact"
+ assert activity_data["id"] == data["id"]
+ assert activity_data["object"] == activity.data["object"]
+ assert activity_data["content"] == "🍮"
+ end
+
+ test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReacts" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
+
+ data =
+ File.read!("test/fixtures/misskey-like.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+ |> Map.put("_misskey_reaction", "⭐")
+
+ _actor = insert(:user, ap_id: data["actor"], local: false)
+
+ {:ok, %Activity{data: activity_data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert activity_data["actor"] == data["actor"]
+ assert activity_data["type"] == "EmojiReact"
+ assert activity_data["id"] == data["id"]
+ assert activity_data["object"] == activity.data["object"]
+ assert activity_data["content"] == "⭐"
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
new file mode 100644
index 0000000..7c406fb
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
@@ -0,0 +1,748 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.CommonAPI
+
+ import Mock
+ import Pleroma.Factory
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ setup do: clear_config([:instance, :max_remote_account_fields])
+
+ describe "handle_incoming" do
+ test "it works for incoming notices with tag not being an array (kroeg)" do
+ data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"], fetch: false)
+
+ assert object.data["emoji"] == %{
+ "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
+ }
+
+ data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"], fetch: false)
+
+ assert "test" in Object.tags(object)
+ assert Object.hashtags(object) == ["test"]
+ end
+
+ test "it ignores an incoming notice if we already have it" do
+ activity = insert(:note_activity)
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", Object.normalize(activity, fetch: false).data)
+
+ {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
+
+ assert activity == returned_activity
+ end
+
+ @tag capture_log: true
+ test "it fetches reply-to activities if we don't have them" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+
+ object =
+ data["object"]
+ |> Map.put("inReplyTo", "https://mstdn.io/users/mayuutann/statuses/99568293732299394")
+
+ data = Map.put(data, "object", object)
+ {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
+ returned_object = Object.normalize(returned_activity, fetch: false)
+
+ assert %Activity{} =
+ Activity.get_create_by_object_ap_id(
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
+ )
+
+ assert returned_object.data["inReplyTo"] ==
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
+ end
+
+ test "it does not fetch reply-to activities beyond max replies depth limit" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+
+ object =
+ data["object"]
+ |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
+
+ data = Map.put(data, "object", object)
+
+ with_mock Pleroma.Web.Federator,
+ allowed_thread_distance?: fn _ -> false end do
+ {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
+
+ returned_object = Object.normalize(returned_activity, fetch: false)
+
+ refute Activity.get_create_by_object_ap_id(
+ "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
+ )
+
+ assert returned_object.data["inReplyTo"] == "https://shitposter.club/notice/2827873"
+ end
+ end
+
+ test "it does not crash if the object in inReplyTo can't be fetched" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+
+ object =
+ data["object"]
+ |> Map.put("inReplyTo", "https://404.site/whatever")
+
+ data =
+ data
+ |> Map.put("object", object)
+
+ assert {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it does not work for deactivated users" do
+ data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
+
+ insert(:user, ap_id: data["actor"], is_active: false)
+
+ assert {:error, _} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it works for incoming notices" do
+ data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["id"] ==
+ "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
+
+ assert data["context"] ==
+ "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
+
+ assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+
+ assert data["cc"] == [
+ "http://localtesting.pleroma.lol/users/lain",
+ "http://mastodon.example.org/users/admin/followers"
+ ]
+
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+
+ object_data = Object.normalize(data["object"], fetch: false).data
+
+ assert object_data["id"] ==
+ "http://mastodon.example.org/users/admin/statuses/99512778738411822"
+
+ assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+
+ assert object_data["cc"] == [
+ "http://localtesting.pleroma.lol/users/lain",
+ "http://mastodon.example.org/users/admin/followers"
+ ]
+
+ assert object_data["actor"] == "http://mastodon.example.org/users/admin"
+ assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
+
+ assert object_data["context"] ==
+ "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
+
+ assert object_data["sensitive"] == true
+
+ user = User.get_cached_by_ap_id(object_data["actor"])
+
+ assert user.note_count == 1
+ end
+
+ test "it works for incoming notices without the sensitive property but an nsfw hashtag" do
+ data = File.read!("test/fixtures/mastodon-post-activity-nsfw.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ object_data = Object.normalize(data["object"], fetch: false).data
+
+ assert object_data["sensitive"] == true
+ end
+
+ test "it works for incoming notices with hashtags" do
+ data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"], fetch: false)
+
+ assert match?(
+ %{
+ "href" => "http://localtesting.pleroma.lol/users/lain",
+ "name" => "@lain@localtesting.pleroma.lol",
+ "type" => "Mention"
+ },
+ Enum.at(object.data["tag"], 0)
+ )
+
+ assert match?(
+ %{
+ "href" => "http://mastodon.example.org/tags/moo",
+ "name" => "#moo",
+ "type" => "Hashtag"
+ },
+ Enum.at(object.data["tag"], 1)
+ )
+
+ assert "moo" == Enum.at(object.data["tag"], 2)
+ end
+
+ test "it works for incoming notices with contentMap" do
+ data = File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"], fetch: false)
+
+ assert object.data["content"] ==
+ "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
+ end
+
+ test "it works for incoming notices with to/cc not being an array (kroeg)" do
+ data = File.read!("test/fixtures/kroeg-post-activity.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"], fetch: false)
+
+ assert object.data["content"] ==
+ "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
+ end
+
+ test "it ensures that as:Public activities make it to their followers collection" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
+ |> Map.put("cc", [])
+
+ object =
+ data["object"]
+ |> Map.put("attributedTo", user.ap_id)
+ |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
+ |> Map.put("cc", [])
+ |> Map.put("id", user.ap_id <> "/activities/12345678")
+
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["cc"] == [User.ap_followers(user)]
+ end
+
+ test "it ensures that address fields become lists" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("cc", nil)
+
+ object =
+ data["object"]
+ |> Map.put("attributedTo", user.ap_id)
+ |> Map.put("cc", nil)
+ |> Map.put("id", user.ap_id <> "/activities/12345678")
+
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ refute is_nil(data["cc"])
+ end
+
+ test "it strips internal likes" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+
+ likes = %{
+ "first" =>
+ "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
+ "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
+ "totalItems" => 3,
+ "type" => "OrderedCollection"
+ }
+
+ object = Map.put(data["object"], "likes", likes)
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)
+
+ object = Object.normalize(activity)
+
+ assert object.data["likes"] == []
+ end
+
+ test "it strips internal reactions" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "📢")
+
+ %{object: object} = Activity.get_by_id_with_object(activity.id)
+ assert Map.has_key?(object.data, "reactions")
+ assert Map.has_key?(object.data, "reaction_count")
+
+ object_data = Transmogrifier.strip_internal_fields(object.data)
+ refute Map.has_key?(object_data, "reactions")
+ refute Map.has_key?(object_data, "reaction_count")
+ end
+
+ test "it correctly processes messages with non-array to field" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+ |> Map.put("to", "https://www.w3.org/ns/activitystreams#Public")
+ |> put_in(["object", "to"], "https://www.w3.org/ns/activitystreams#Public")
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(data)
+
+ assert [
+ "http://localtesting.pleroma.lol/users/lain",
+ "http://mastodon.example.org/users/admin/followers"
+ ] == activity.data["cc"]
+
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
+ end
+
+ test "it correctly processes messages with non-array cc field" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+ |> Map.put("cc", "http://mastodon.example.org/users/admin/followers")
+ |> put_in(["object", "cc"], "http://mastodon.example.org/users/admin/followers")
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(data)
+
+ assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"]
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
+ end
+
+ test "it correctly processes messages with weirdness in address fields" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+ |> Map.put("cc", ["http://mastodon.example.org/users/admin/followers", ["¿"]])
+ |> put_in(["object", "cc"], ["http://mastodon.example.org/users/admin/followers", ["¿"]])
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(data)
+
+ assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"]
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
+ end
+ end
+
+ describe "`handle_incoming/2`, Mastodon format `replies` handling" do
+ setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
+ setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
+
+ setup do
+ data =
+ "test/fixtures/mastodon-post-activity.json"
+ |> File.read!()
+ |> Jason.decode!()
+
+ items = get_in(data, ["object", "replies", "first", "items"])
+ assert length(items) > 0
+
+ %{data: data, items: items}
+ end
+
+ test "schedules background fetching of `replies` items if max thread depth limit allows", %{
+ data: data,
+ items: items
+ } do
+ clear_config([:instance, :federation_incoming_replies_max_depth], 10)
+
+ {:ok, activity} = Transmogrifier.handle_incoming(data)
+
+ object = Object.normalize(activity.data["object"])
+
+ assert object.data["replies"] == items
+
+ for id <- items do
+ job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
+ assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
+ end
+ end
+
+ test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
+ %{data: data} do
+ clear_config([:instance, :federation_incoming_replies_max_depth], 0)
+
+ {:ok, _activity} = Transmogrifier.handle_incoming(data)
+
+ assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
+ end
+ end
+
+ describe "`handle_incoming/2`, Pleroma format `replies` handling" do
+ setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
+ setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
+
+ setup do
+ replies = %{
+ "type" => "Collection",
+ "items" => [Utils.generate_object_id(), Utils.generate_object_id()]
+ }
+
+ activity =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+ |> Kernel.put_in(["object", "replies"], replies)
+
+ %{activity: activity}
+ end
+
+ test "schedules background fetching of `replies` items if max thread depth limit allows", %{
+ activity: activity
+ } do
+ clear_config([:instance, :federation_incoming_replies_max_depth], 1)
+
+ assert {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(activity)
+ object = Object.normalize(data["object"])
+
+ for id <- object.data["replies"] do
+ job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
+ assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
+ end
+ end
+
+ test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
+ %{activity: activity} do
+ clear_config([:instance, :federation_incoming_replies_max_depth], 0)
+
+ {:ok, _activity} = Transmogrifier.handle_incoming(activity)
+
+ assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
+ end
+ end
+
+ describe "reserialization" do
+ test "successfully reserializes a message with inReplyTo == nil" do
+ user = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Create",
+ "object" => %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "id" => Utils.generate_object_id(),
+ "type" => "Note",
+ "content" => "Hi",
+ "inReplyTo" => nil,
+ "attributedTo" => user.ap_id
+ },
+ "actor" => user.ap_id
+ }
+
+ {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
+ end
+
+ test "successfully reserializes a message with AS2 objects in IR" do
+ user = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Create",
+ "object" => %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "id" => Utils.generate_object_id(),
+ "type" => "Note",
+ "content" => "Hi",
+ "inReplyTo" => nil,
+ "attributedTo" => user.ap_id,
+ "tag" => [
+ %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
+ %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
+ ]
+ },
+ "actor" => user.ap_id
+ }
+
+ {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
+ end
+ end
+
+ describe "fix_in_reply_to/2" do
+ setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
+
+ setup do
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ [data: data]
+ end
+
+ test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
+ assert Transmogrifier.fix_in_reply_to(data) == data
+ end
+
+ test "returns object with inReplyTo when denied incoming reply", %{data: data} do
+ clear_config([:instance, :federation_incoming_replies_max_depth], 0)
+
+ object_with_reply =
+ Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
+
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
+
+ object_with_reply =
+ Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
+
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
+
+ object_with_reply =
+ Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
+
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
+
+ object_with_reply = Map.put(data["object"], "inReplyTo", [])
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == []
+ end
+
+ @tag capture_log: true
+ test "returns modified object when allowed incoming reply", %{data: data} do
+ object_with_reply =
+ Map.put(
+ data["object"],
+ "inReplyTo",
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
+ )
+
+ clear_config([:instance, :federation_incoming_replies_max_depth], 5)
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+
+ assert modified_object["inReplyTo"] ==
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
+
+ assert modified_object["context"] ==
+ "tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4"
+ end
+ end
+
+ describe "fix_attachments/1" do
+ test "returns not modified object" do
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ assert Transmogrifier.fix_attachments(data) == data
+ end
+
+ test "returns modified object when attachment is map" do
+ assert Transmogrifier.fix_attachments(%{
+ "attachment" => %{
+ "mediaType" => "video/mp4",
+ "url" => "https://peertube.moe/stat-480.mp4"
+ }
+ }) == %{
+ "attachment" => [
+ %{
+ "mediaType" => "video/mp4",
+ "type" => "Document",
+ "url" => [
+ %{
+ "href" => "https://peertube.moe/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ]
+ }
+ ]
+ }
+ end
+
+ test "returns modified object when attachment is list" do
+ assert Transmogrifier.fix_attachments(%{
+ "attachment" => [
+ %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
+ %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
+ ]
+ }) == %{
+ "attachment" => [
+ %{
+ "mediaType" => "video/mp4",
+ "type" => "Document",
+ "url" => [
+ %{
+ "href" => "https://pe.er/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ]
+ },
+ %{
+ "mediaType" => "video/mp4",
+ "type" => "Document",
+ "url" => [
+ %{
+ "href" => "https://pe.er/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ]
+ }
+ ]
+ }
+ end
+ end
+
+ describe "fix_emoji/1" do
+ test "returns not modified object when object not contains tags" do
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ assert Transmogrifier.fix_emoji(data) == data
+ end
+
+ test "returns object with emoji when object contains list tags" do
+ assert Transmogrifier.fix_emoji(%{
+ "tag" => [
+ %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
+ %{"type" => "Hashtag"}
+ ]
+ }) == %{
+ "emoji" => %{"bib" => "/test"},
+ "tag" => [
+ %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
+ %{"type" => "Hashtag"}
+ ]
+ }
+ end
+
+ test "returns object with emoji when object contains map tag" do
+ assert Transmogrifier.fix_emoji(%{
+ "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
+ }) == %{
+ "emoji" => %{"bib" => "/test"},
+ "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
+ }
+ end
+ end
+
+ describe "set_replies/1" do
+ setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
+
+ test "returns unmodified object if activity doesn't have self-replies" do
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ assert Transmogrifier.set_replies(data) == data
+ end
+
+ test "sets `replies` collection with a limited number of self-replies" do
+ [user, another_user] = insert_list(2, :user)
+
+ {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
+
+ {:ok, %{id: id2} = self_reply1} =
+ CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1})
+
+ {:ok, self_reply2} =
+ CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1})
+
+ # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
+ {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1})
+
+ {:ok, _} =
+ CommonAPI.post(user, %{
+ status: "self-reply to self-reply",
+ in_reply_to_status_id: id2
+ })
+
+ {:ok, _} =
+ CommonAPI.post(another_user, %{
+ status: "another user's reply",
+ in_reply_to_status_id: id1
+ })
+
+ object = Object.normalize(activity, fetch: false)
+ replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
+
+ assert %{"type" => "Collection", "items" => ^replies_uris} =
+ Transmogrifier.set_replies(object.data)["replies"]
+ end
+ end
+
+ test "take_emoji_tags/1" do
+ user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
+
+ assert Transmogrifier.take_emoji_tags(user) == [
+ %{
+ "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
+ "id" => "https://example.org/firefox.png",
+ "name" => ":firefox:",
+ "type" => "Emoji",
+ "updated" => "1970-01-01T00:00:00Z"
+ }
+ ]
+ end
+
+ test "the standalone note uses its own ID when context is missing" do
+ insert(:user, ap_id: "https://mk.absturztau.be/users/8ozbzjs3o8")
+
+ activity =
+ "test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json"
+ |> File.read!()
+ |> Jason.decode!()
+
+ {:ok, %Activity{} = modified} = Transmogrifier.handle_incoming(activity)
+ object = Object.normalize(modified, fetch: false)
+
+ assert object.data["context"] == object.data["id"]
+ assert modified.data["context"] == object.data["id"]
+ end
+
+ test "the reply note uses its parent's ID when context is missing and reply is unreachable" do
+ insert(:user, ap_id: "https://mk.absturztau.be/users/8ozbzjs3o8")
+
+ activity =
+ "test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json"
+ |> File.read!()
+ |> Jason.decode!()
+
+ object =
+ activity["object"]
+ |> Map.put("inReplyTo", "https://404.site/object/went-to-buy-milk")
+
+ activity =
+ activity
+ |> Map.put("object", object)
+
+ {:ok, %Activity{} = modified} = Transmogrifier.handle_incoming(activity)
+ object = Object.normalize(modified, fetch: false)
+
+ assert object.data["context"] == object.data["inReplyTo"]
+ assert modified.data["context"] == object.data["inReplyTo"]
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs
new file mode 100644
index 0000000..d50c7f4
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.PageHandlingTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase
+
+ alias Pleroma.Object.Fetcher
+
+ test "Lemmy Page" do
+ Tesla.Mock.mock(fn
+ %{url: "https://enterprise.lemmy.ml/post/3"} ->
+ %Tesla.Env{
+ status: 200,
+ headers: [{"content-type", "application/activity+json"}],
+ body: File.read!("test/fixtures/tesla_mock/lemmy-page.json")
+ }
+
+ %{url: "https://enterprise.lemmy.ml/u/nutomic"} ->
+ %Tesla.Env{
+ status: 200,
+ headers: [{"content-type", "application/activity+json"}],
+ body: File.read!("test/fixtures/tesla_mock/lemmy-user.json")
+ }
+ end)
+
+ {:ok, object} = Fetcher.fetch_object_from_id("https://enterprise.lemmy.ml/post/3")
+
+ assert object.data["summary"] == "Hello Federation!"
+ assert object.data["published"] == "2020-09-14T15:03:11.909105Z"
+
+ # WAT
+ assert object.data["url"] == "https://enterprise.lemmy.ml/pictrs/image/US52d9DPvf.jpg"
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs
new file mode 100644
index 0000000..d310705
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs
@@ -0,0 +1,173 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.QuestionHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ test "Mastodon Question activity" do
+ data = File.read!("test/fixtures/mastodon-question-activity.json") |> Jason.decode!()
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ object = Object.normalize(activity, fetch: false)
+
+ assert object.data["url"] == "https://mastodon.sdf.org/@rinpatch/102070944809637304"
+
+ assert object.data["closed"] == "2019-05-11T09:03:36Z"
+
+ assert object.data["context"] == activity.data["context"]
+
+ assert object.data["context"] ==
+ "tag:mastodon.sdf.org,2019-05-10:objectId=15095122:objectType=Conversation"
+
+ assert object.data["anyOf"] == []
+
+ assert Enum.sort(object.data["oneOf"]) ==
+ Enum.sort([
+ %{
+ "name" => "25 char limit is dumb",
+ "replies" => %{"totalItems" => 0, "type" => "Collection"},
+ "type" => "Note"
+ },
+ %{
+ "name" => "Dunno",
+ "replies" => %{"totalItems" => 0, "type" => "Collection"},
+ "type" => "Note"
+ },
+ %{
+ "name" => "Everyone knows that!",
+ "replies" => %{"totalItems" => 1, "type" => "Collection"},
+ "type" => "Note"
+ },
+ %{
+ "name" => "I can't even fit a funny",
+ "replies" => %{"totalItems" => 1, "type" => "Collection"},
+ "type" => "Note"
+ }
+ ])
+
+ user = insert(:user)
+
+ {:ok, reply_activity} = CommonAPI.post(user, %{status: "hewwo", in_reply_to_id: activity.id})
+
+ reply_object = Object.normalize(reply_activity, fetch: false)
+
+ assert reply_object.data["context"] == object.data["context"]
+ end
+
+ test "Mastodon Question activity with HTML tags in plaintext" do
+ options = [
+ %{
+ "type" => "Note",
+ "name" => "<input type=\"date\">",
+ "replies" => %{"totalItems" => 0, "type" => "Collection"}
+ },
+ %{
+ "type" => "Note",
+ "name" => "<input type=\"date\"/>",
+ "replies" => %{"totalItems" => 0, "type" => "Collection"}
+ },
+ %{
+ "type" => "Note",
+ "name" => "<input type=\"date\" />",
+ "replies" => %{"totalItems" => 1, "type" => "Collection"}
+ },
+ %{
+ "type" => "Note",
+ "name" => "<input type=\"date\"></input>",
+ "replies" => %{"totalItems" => 1, "type" => "Collection"}
+ }
+ ]
+
+ data =
+ File.read!("test/fixtures/mastodon-question-activity.json")
+ |> Jason.decode!()
+ |> Kernel.put_in(["object", "oneOf"], options)
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(activity, fetch: false)
+
+ assert Enum.sort(object.data["oneOf"]) == Enum.sort(options)
+ end
+
+ test "Mastodon Question activity with custom emojis" do
+ options = [
+ %{
+ "type" => "Note",
+ "name" => ":blobcat:",
+ "replies" => %{"totalItems" => 0, "type" => "Collection"}
+ },
+ %{
+ "type" => "Note",
+ "name" => ":blobfox:",
+ "replies" => %{"totalItems" => 0, "type" => "Collection"}
+ }
+ ]
+
+ tag = [
+ %{
+ "icon" => %{
+ "type" => "Image",
+ "url" => "https://blob.cat/emoji/custom/blobcats/blobcat.png"
+ },
+ "id" => "https://blob.cat/emoji/custom/blobcats/blobcat.png",
+ "name" => ":blobcat:",
+ "type" => "Emoji",
+ "updated" => "1970-01-01T00:00:00Z"
+ },
+ %{
+ "icon" => %{"type" => "Image", "url" => "https://blob.cat/emoji/blobfox/blobfox.png"},
+ "id" => "https://blob.cat/emoji/blobfox/blobfox.png",
+ "name" => ":blobfox:",
+ "type" => "Emoji",
+ "updated" => "1970-01-01T00:00:00Z"
+ }
+ ]
+
+ data =
+ File.read!("test/fixtures/mastodon-question-activity.json")
+ |> Jason.decode!()
+ |> Kernel.put_in(["object", "oneOf"], options)
+ |> Kernel.put_in(["object", "tag"], tag)
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(activity, fetch: false)
+
+ assert object.data["oneOf"] == options
+
+ assert object.data["emoji"] == %{
+ "blobcat" => "https://blob.cat/emoji/custom/blobcats/blobcat.png",
+ "blobfox" => "https://blob.cat/emoji/blobfox/blobfox.png"
+ }
+ end
+
+ test "returns same activity if received a second time" do
+ data = File.read!("test/fixtures/mastodon-question-activity.json") |> Jason.decode!()
+
+ assert {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ assert {:ok, ^activity} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "accepts a Question with no content" do
+ data =
+ File.read!("test/fixtures/mastodon-question-activity.json")
+ |> Jason.decode!()
+ |> Kernel.put_in(["object", "content"], "")
+
+ assert {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs
new file mode 100644
index 0000000..1156242
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs
@@ -0,0 +1,67 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.RejectHandlingTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Activity
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ test "it fails for incoming rejects which cannot be correlated" do
+ follower = insert(:user)
+ followed = insert(:user, is_locked: true)
+
+ accept_data =
+ File.read!("test/fixtures/mastodon-reject-activity.json")
+ |> Jason.decode!()
+ |> Map.put("actor", followed.ap_id)
+
+ accept_data =
+ Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
+
+ {:error, _} = Transmogrifier.handle_incoming(accept_data)
+
+ follower = User.get_cached_by_id(follower.id)
+
+ refute User.following?(follower, followed) == true
+ end
+
+ test "it works for incoming rejects which are referenced by IRI only" do
+ follower = insert(:user)
+ followed = insert(:user, is_locked: true)
+
+ {:ok, follower, followed} = User.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
+
+ assert User.following?(follower, followed) == true
+
+ reject_data =
+ File.read!("test/fixtures/mastodon-reject-activity.json")
+ |> Jason.decode!()
+ |> Map.put("actor", followed.ap_id)
+ |> Map.put("object", follow_activity.data["id"])
+
+ {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
+
+ follower = User.get_cached_by_id(follower.id)
+
+ assert User.following?(follower, followed) == false
+ end
+
+ test "it rejects activities without a valid ID" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+ |> Map.put("id", "")
+
+ :error = Transmogrifier.handle_incoming(data)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs
new file mode 100644
index 0000000..846d25c
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs
@@ -0,0 +1,185 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.UndoHandlingTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ test "it works for incoming emoji reaction undos" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
+ {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, user, "👌")
+
+ data =
+ File.read!("test/fixtures/mastodon-undo-like.json")
+ |> Jason.decode!()
+ |> Map.put("object", reaction_activity.data["id"])
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, activity} = Transmogrifier.handle_incoming(data)
+
+ assert activity.actor == user.ap_id
+ assert activity.data["id"] == data["id"]
+ assert activity.data["type"] == "Undo"
+ end
+
+ test "it returns an error for incoming unlikes wihout a like activity" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "leave a like pls"})
+
+ data =
+ File.read!("test/fixtures/mastodon-undo-like.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+
+ assert Transmogrifier.handle_incoming(data) == :error
+ end
+
+ test "it works for incoming unlikes with an existing like activity" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "leave a like pls"})
+
+ like_data =
+ File.read!("test/fixtures/mastodon-like.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+
+ _liker = insert(:user, ap_id: like_data["actor"], local: false)
+
+ {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
+
+ data =
+ File.read!("test/fixtures/mastodon-undo-like.json")
+ |> Jason.decode!()
+ |> Map.put("object", like_data)
+ |> Map.put("actor", like_data["actor"])
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+ assert data["type"] == "Undo"
+ assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
+ assert data["object"] == "http://mastodon.example.org/users/admin#likes/2"
+
+ note = Object.get_by_ap_id(like_data["object"])
+ assert note.data["like_count"] == 0
+ assert note.data["likes"] == []
+ end
+
+ test "it works for incoming unlikes with an existing like activity and a compact object" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "leave a like pls"})
+
+ like_data =
+ File.read!("test/fixtures/mastodon-like.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+
+ _liker = insert(:user, ap_id: like_data["actor"], local: false)
+
+ {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
+
+ data =
+ File.read!("test/fixtures/mastodon-undo-like.json")
+ |> Jason.decode!()
+ |> Map.put("object", like_data["id"])
+ |> Map.put("actor", like_data["actor"])
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+ assert data["type"] == "Undo"
+ assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
+ assert data["object"] == "http://mastodon.example.org/users/admin#likes/2"
+ end
+
+ test "it works for incoming unannounces with an existing notice" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
+
+ announce_data =
+ File.read!("test/fixtures/mastodon-announce.json")
+ |> Jason.decode!()
+ |> Map.put("object", activity.data["object"])
+
+ _announcer = insert(:user, ap_id: announce_data["actor"], local: false)
+
+ {:ok, %Activity{data: announce_data, local: false}} =
+ Transmogrifier.handle_incoming(announce_data)
+
+ data =
+ File.read!("test/fixtures/mastodon-undo-announce.json")
+ |> Jason.decode!()
+ |> Map.put("object", announce_data)
+ |> Map.put("actor", announce_data["actor"])
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["type"] == "Undo"
+
+ assert data["object"] ==
+ "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
+ end
+
+ test "it works for incoming unfollows with an existing follow" do
+ user = insert(:user)
+
+ follow_data =
+ File.read!("test/fixtures/mastodon-follow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+
+ _follower = insert(:user, ap_id: follow_data["actor"], local: false)
+
+ {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
+
+ data =
+ File.read!("test/fixtures/mastodon-unfollow-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", follow_data)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["type"] == "Undo"
+ assert data["object"]["type"] == "Follow"
+ assert data["object"]["object"] == user.ap_id
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+
+ refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
+ end
+
+ test "it works for incoming unblocks with an existing block" do
+ user = insert(:user)
+
+ block_data =
+ File.read!("test/fixtures/mastodon-block-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", user.ap_id)
+
+ _blocker = insert(:user, ap_id: block_data["actor"], local: false)
+
+ {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
+
+ data =
+ File.read!("test/fixtures/mastodon-unblock-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", block_data)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ assert data["type"] == "Undo"
+ assert data["object"] == block_data["id"]
+
+ blocker = User.get_cached_by_ap_id(data["actor"])
+
+ refute User.blocks?(blocker, user)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs
new file mode 100644
index 0000000..da46f06
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs
@@ -0,0 +1,159 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.UserUpdateHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Pleroma.Factory
+
+ test "it works for incoming update activities" do
+ user = insert(:user, local: false)
+
+ update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
+
+ object =
+ update_data["object"]
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("id", user.ap_id)
+
+ update_data =
+ update_data
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("object", object)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
+
+ assert data["id"] == update_data["id"]
+
+ user = User.get_cached_by_ap_id(data["actor"])
+ assert user.name == "gargle"
+
+ assert user.avatar["url"] == [
+ %{
+ "href" =>
+ "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
+ }
+ ]
+
+ assert user.banner["url"] == [
+ %{
+ "href" =>
+ "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
+ }
+ ]
+
+ assert user.bio == "<p>Some bio</p>"
+ end
+
+ test "it works with alsoKnownAs" do
+ %{ap_id: actor} = insert(:user, local: false)
+
+ assert User.get_cached_by_ap_id(actor).also_known_as == []
+
+ {:ok, _activity} =
+ "test/fixtures/mastodon-update.json"
+ |> File.read!()
+ |> Jason.decode!()
+ |> Map.put("actor", actor)
+ |> Map.update!("object", fn object ->
+ object
+ |> Map.put("actor", actor)
+ |> Map.put("id", actor)
+ |> Map.put("alsoKnownAs", [
+ "http://mastodon.example.org/users/foo",
+ "http://example.org/users/bar"
+ ])
+ end)
+ |> Transmogrifier.handle_incoming()
+
+ assert User.get_cached_by_ap_id(actor).also_known_as == [
+ "http://mastodon.example.org/users/foo",
+ "http://example.org/users/bar"
+ ]
+ end
+
+ test "it works with custom profile fields" do
+ user = insert(:user, local: false)
+
+ assert user.fields == []
+
+ update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
+
+ object =
+ update_data["object"]
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("id", user.ap_id)
+
+ update_data =
+ update_data
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("object", object)
+
+ {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data)
+
+ user = User.get_cached_by_ap_id(user.ap_id)
+
+ assert user.fields == [
+ %{"name" => "foo", "value" => "updated"},
+ %{"name" => "foo1", "value" => "updated"}
+ ]
+
+ clear_config([:instance, :max_remote_account_fields], 2)
+
+ update_data =
+ update_data
+ |> put_in(["object", "attachment"], [
+ %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
+ %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"},
+ %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"}
+ ])
+ |> Map.put("id", update_data["id"] <> ".")
+
+ {:ok, _} = Transmogrifier.handle_incoming(update_data)
+
+ user = User.get_cached_by_ap_id(user.ap_id)
+
+ assert user.fields == [
+ %{"name" => "foo", "value" => "updated"},
+ %{"name" => "foo1", "value" => "updated"}
+ ]
+
+ update_data =
+ update_data
+ |> put_in(["object", "attachment"], [])
+ |> Map.put("id", update_data["id"] <> ".")
+
+ {:ok, _} = Transmogrifier.handle_incoming(update_data)
+
+ user = User.get_cached_by_ap_id(user.ap_id)
+
+ assert user.fields == []
+ end
+
+ test "it works for incoming update activities which lock the account" do
+ user = insert(:user, local: false)
+
+ update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
+
+ object =
+ update_data["object"]
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("id", user.ap_id)
+ |> Map.put("manuallyApprovesFollowers", true)
+
+ update_data =
+ update_data
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("object", object)
+
+ {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(update_data)
+
+ user = User.get_cached_by_ap_id(user.ap_id)
+ assert user.is_locked == true
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs
new file mode 100644
index 0000000..57b5171
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs
@@ -0,0 +1,122 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.VideoHandlingTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Object.Fetcher
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ test "skip converting the content when it is nil" do
+ data =
+ File.read!("test/fixtures/tesla_mock/framatube.org-video.json")
+ |> Jason.decode!()
+ |> Kernel.put_in(["object", "content"], nil)
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ assert object = Object.normalize(activity, fetch: false)
+
+ assert object.data["content"] == nil
+ end
+
+ test "it converts content of object to html" do
+ data = File.read!("test/fixtures/tesla_mock/framatube.org-video.json") |> Jason.decode!()
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ assert object = Object.normalize(activity, fetch: false)
+
+ assert object.data["content"] ==
+ "<p>Après avoir mené avec un certain succès la campagne « Dégooglisons Internet » en 2014, l’association Framasoft annonce fin 2019 arrêter progressivement un certain nombre de ses services alternatifs aux GAFAM. Pourquoi ?</p><p>Transcription par @aprilorg ici : <a href=\"https://www.april.org/deframasoftisons-internet-pierre-yves-gosset-framasoft\">https://www.april.org/deframasoftisons-internet-pierre-yves-gosset-framasoft</a></p>"
+ end
+
+ test "it remaps video URLs as attachments if necessary" do
+ {:ok, object} =
+ Fetcher.fetch_object_from_id(
+ "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
+ )
+
+ assert object.data["url"] ==
+ "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
+
+ assert object.data["attachment"] == [
+ %{
+ "type" => "Link",
+ "mediaType" => "video/mp4",
+ "url" => [
+ %{
+ "href" =>
+ "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link",
+ "width" => 480
+ }
+ ]
+ }
+ ]
+
+ data = File.read!("test/fixtures/tesla_mock/framatube.org-video.json") |> Jason.decode!()
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ assert object = Object.normalize(activity, fetch: false)
+
+ assert object.data["attachment"] == [
+ %{
+ "type" => "Link",
+ "mediaType" => "video/mp4",
+ "url" => [
+ %{
+ "href" =>
+ "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link",
+ "height" => 1080
+ }
+ ]
+ }
+ ]
+
+ assert object.data["url"] ==
+ "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
+ end
+
+ test "it works for peertube videos with only their mpegURL map" do
+ data =
+ File.read!("test/fixtures/peertube/video-object-mpegURL-only.json")
+ |> Jason.decode!()
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ assert object = Object.normalize(activity, fetch: false)
+
+ assert object.data["attachment"] == [
+ %{
+ "type" => "Link",
+ "mediaType" => "video/mp4",
+ "url" => [
+ %{
+ "href" =>
+ "https://peertube.stream/static/streaming-playlists/hls/abece3c3-b9c6-47f4-8040-f3eed8c602e6/abece3c3-b9c6-47f4-8040-f3eed8c602e6-1080-fragmented.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link",
+ "height" => 1080
+ }
+ ]
+ }
+ ]
+
+ assert object.data["url"] ==
+ "https://peertube.stream/videos/watch/abece3c3-b9c6-47f4-8040-f3eed8c602e6"
+ end
+end