diff options
Diffstat (limited to 'test/pleroma/web/activity_pub/transmogrifier')
17 files changed, 1928 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 000000000..c6ff96f08 --- /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-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.AcceptHandlingTest do + use Pleroma.DataCase + + 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} = 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") + |> Poison.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") + |> Poison.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") + |> Poison.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/announce_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs new file mode 100644 index 000000000..99c296c74 --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs @@ -0,0 +1,176 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 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") |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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"]) + + 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") + |> Poison.decode!() + + _user = insert(:user, local: false, ap_id: data["actor"]) + + assert {:error, _e} = Transmogrifier.handle_incoming(data) + end + + test "it does not clobber the addressing on announce activities" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) + + data = + File.read!("test/fixtures/mastodon-announce.json") + |> Poison.decode!() + |> Map.put("object", Object.normalize(activity).data["id"]) + |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"]) + |> Map.put("cc", []) + + _user = + insert(:user, + local: false, + ap_id: data["actor"], + follower_address: "http://mastodon.example.org/users/admin/followers" + ) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["to"] == ["http://mastodon.example.org/users/admin/followers"] + 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 000000000..0f6605c3f --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs @@ -0,0 +1,78 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 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) + + data = + File.read!("test/fixtures/mastodon-vote.json") + |> Poison.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) + 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["replies_count"] == object.data["replies_count"] + + 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) + # TODO: Replace with CommonAPI vote creation when implemented + data = + File.read!("test/fixtures/mastodon-vote.json") + |> Poison.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 000000000..b0ae804c5 --- /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-2020 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"]) + + 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"]) + + 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 000000000..181eb7b09 --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs @@ -0,0 +1,84 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 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", + "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) + + 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") |> Poison.decode!() + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + + assert object = Object.normalize(activity, false) + + assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"] + + assert object.data["cc"] == [] + + assert object.data["url"] == "https://channels.tests.funkwhale.audio/library/tracks/74" + + assert object.data["attachment"] == [ + %{ + "mediaType" => "audio/ogg", + "type" => "Link", + "name" => nil, + "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 000000000..71f1a0ed5 --- /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-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.BlockHandlingTest do + use Pleroma.DataCase + + 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") + |> Poison.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") + |> Poison.decode!() + |> Map.put("object", blocked.ap_id) + |> Map.put("actor", blocker.ap_id) + + {:ok, blocker} = User.follow(blocker, blocked) + {:ok, blocked} = 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 000000000..31274c067 --- /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-2020 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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.decode!() + + _author = + insert(:user, + ap_id: data["actor"], + local: false, + last_refreshed_at: DateTime.utc_now(), + deactivated: true + ) + + _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") + |> Poison.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('XSS')" + 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 000000000..c9a53918c --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs @@ -0,0 +1,114 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 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") + |> Poison.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"]) + 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"]) + |> Repo.delete() + + Cachex.del(:object_cache, "object:#{object.data["id"]}") + + deleting_user = insert(:user) + + data = + File.read!("test/fixtures/mastodon-delete.json") + |> Poison.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") + |> Poison.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") + |> Poison.decode!() + + {:ok, _} = Transmogrifier.handle_incoming(data) + ObanHelpers.perform_all() + + assert User.get_cached_by_ap_id(ap_id).deactivated + 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") + |> Poison.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 000000000..0fb056b50 --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest 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 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") + |> Poison.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 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") + |> Poison.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") + |> Poison.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 000000000..d7c55cfbe --- /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-2020 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"] == [] + + 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 000000000..4ef8210ad --- /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-2020 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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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 + Pleroma.Config.put([: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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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 000000000..53fe1d550 --- /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-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do + use Pleroma.DataCase + + 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") + |> Poison.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") + |> Poison.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") + |> Poison.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/question_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs new file mode 100644 index 000000000..d2822ce75 --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs @@ -0,0 +1,176 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 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") |> Poison.decode!() + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + + object = Object.normalize(activity, 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["context_id"] + + 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, false) + + assert reply_object.data["context"] == object.data["context"] + assert reply_object.data["context_id"] == object.data["context_id"] + 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") + |> Poison.decode!() + |> Kernel.put_in(["object", "oneOf"], options) + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + object = Object.normalize(activity, 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") + |> Poison.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, 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") |> Poison.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") + |> Poison.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 000000000..5c1451def --- /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-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.RejectHandlingTest do + use Pleroma.DataCase + + 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") + |> Poison.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} = 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") + |> Poison.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") + |> Poison.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 000000000..8683f7135 --- /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-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.UndoHandlingTest do + use Pleroma.DataCase + + 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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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") + |> Poison.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 000000000..7c4d16db7 --- /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-2020 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") |> Poison.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!() + |> Poison.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") |> Poison.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"} + ] + + Pleroma.Config.put([: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") |> Poison.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 000000000..69c953a2e --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs @@ -0,0 +1,93 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 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, 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, 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", + "name" => nil, + "url" => [ + %{ + "href" => + "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ] + + 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, false) + + assert object.data["attachment"] == [ + %{ + "type" => "Link", + "mediaType" => "video/mp4", + "name" => nil, + "url" => [ + %{ + "href" => + "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ] + + assert object.data["url"] == + "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206" + end +end |