# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
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
- import ExUnit.CaptureLog
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
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") |> Poison.decode!()
+ 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"])
+ 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") |> Poison.decode!()
+ 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"])
-
- assert "test" in object.data["tag"]
- end
-
- test "it cleans up incoming notices which are not really DMs" do
- user = insert(:user)
- other_user = insert(:user)
-
- to = [user.ap_id, other_user.ap_id]
-
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
- |> Map.put("to", to)
- |> Map.put("cc", [])
-
- object =
- data["object"]
- |> Map.put("to", to)
- |> Map.put("cc", [])
-
- data = Map.put(data, "object", object)
-
- {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
-
- assert data["to"] == []
- assert data["cc"] == to
-
- object_data = Object.normalize(activity).data
+ object = Object.normalize(data["object"], fetch: false)
- assert object_data["to"] == []
- assert object_data["cc"] == to
+ assert "test" in Object.tags(object)
+ assert Object.hashtags(object) == ["test"]
end
test "it ignores an incoming notice if we already have it" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
- |> Map.put("object", Object.normalize(activity).data)
+ |> Jason.decode!()
+ |> Map.put("object", Object.normalize(activity, fetch: false).data)
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
test "it fetches reply-to activities if we don't have them" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
+ |> Jason.decode!()
object =
data["object"]
data = Map.put(data, "object", object)
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
- returned_object = Object.normalize(returned_activity, false)
+ returned_object = Object.normalize(returned_activity, fetch: false)
assert %Activity{} =
Activity.get_create_by_object_ap_id(
test "it does not fetch reply-to activities beyond max replies depth limit" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
+ |> Jason.decode!()
object =
data["object"]
allowed_thread_distance?: fn _ -> false end do
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
- returned_object = Object.normalize(returned_activity, false)
+ 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"
test "it does not crash if the object in inReplyTo can't be fetched" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
+ |> Jason.decode!()
object =
data["object"]
data
|> Map.put("object", object)
- assert capture_log(fn ->
- {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
- end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil"
+ 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") |> Poison.decode!()
+ data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
- insert(:user, ap_id: data["actor"], deactivated: true)
+ 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") |> Poison.decode!()
+ data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert data["cc"] == [
- "http://mastodon.example.org/users/admin/followers",
- "http://localtesting.pleroma.lol/users/lain"
+ "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"]).data
+ 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://mastodon.example.org/users/admin/followers",
- "http://localtesting.pleroma.lol/users/lain"
+ "http://localtesting.pleroma.lol/users/lain",
+ "http://mastodon.example.org/users/admin/followers"
]
assert object_data["actor"] == "http://mastodon.example.org/users/admin"
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") |> Poison.decode!()
+ 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"], false).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") |> Poison.decode!()
+ 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"])
+ 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 Enum.at(object.data["tag"], 2) == "moo"
+ 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") |> Poison.decode!()
+ 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"])
+ 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") |> Poison.decode!()
+ 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"])
+ 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>"
data =
File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
+ |> Jason.decode!()
|> Map.put("actor", user.ap_id)
|> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
|> Map.put("cc", [])
data =
File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
+ |> Jason.decode!()
|> Map.put("actor", user.ap_id)
- |> Map.put("to", nil)
|> Map.put("cc", nil)
object =
data["object"]
|> Map.put("attributedTo", user.ap_id)
- |> Map.put("to", nil)
|> Map.put("cc", nil)
|> Map.put("id", user.ap_id <> "/activities/12345678")
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
- assert !is_nil(data["to"])
- assert !is_nil(data["cc"])
+ refute is_nil(data["cc"])
end
test "it strips internal likes" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
+ |> Jason.decode!()
likes = %{
"first" =>
object = Map.put(data["object"], "likes", likes)
data = Map.put(data, "object", object)
- {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
+ {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)
+
+ object = Object.normalize(activity)
- refute Map.has_key?(object.data, "likes")
+ assert object.data["likes"] == []
end
test "it strips internal reactions" do
end
test "it correctly processes messages with non-array to field" do
- user = insert(:user)
+ 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")
- message = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "to" => "https://www.w3.org/ns/activitystreams#Public",
- "type" => "Create",
- "object" => %{
- "content" => "blah blah blah",
- "type" => "Note",
- "attributedTo" => user.ap_id,
- "inReplyTo" => nil
- },
- "actor" => user.ap_id
- }
+ assert {:ok, activity} = Transmogrifier.handle_incoming(data)
- assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+ 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
- user = insert(:user)
-
- message = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "to" => user.follower_address,
- "cc" => "https://www.w3.org/ns/activitystreams#Public",
- "type" => "Create",
- "object" => %{
- "content" => "blah blah blah",
- "type" => "Note",
- "attributedTo" => user.ap_id,
- "inReplyTo" => nil
- },
- "actor" => user.ap_id
- }
+ 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(message)
+ assert {:ok, activity} = Transmogrifier.handle_incoming(data)
- assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
- assert [user.follower_address] == activity.data["to"]
+ 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
- user = insert(:user)
-
- message = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "to" => [nil, user.follower_address],
- "cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]],
- "type" => "Create",
- "object" => %{
- "content" => "…",
- "type" => "Note",
- "attributedTo" => user.ap_id,
- "inReplyTo" => nil
- },
- "actor" => user.ap_id
- }
+ 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(message)
+ assert {:ok, activity} = Transmogrifier.handle_incoming(data)
- assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
- assert [user.follower_address] == activity.data["to"]
+ assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"]
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
end
end
data =
"test/fixtures/mastodon-post-activity.json"
|> File.read!()
- |> Poison.decode!()
+ |> Jason.decode!()
items = get_in(data, ["object", "replies", "first", "items"])
assert length(items) > 0
data: data,
items: items
} do
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
+ clear_config([:instance, :federation_incoming_replies_max_depth], 10)
- {:ok, _activity} = Transmogrifier.handle_incoming(data)
+ {: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}
test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
%{data: data} do
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
+ clear_config([:instance, :federation_incoming_replies_max_depth], 0)
{:ok, _activity} = Transmogrifier.handle_incoming(data)
setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
setup do
- user = insert(:user)
-
- {:ok, activity} = CommonAPI.post(user, %{status: "post1"})
-
- {:ok, reply1} =
- CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
-
- {:ok, reply2} =
- CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
-
- replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
-
- {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
+ replies = %{
+ "type" => "Collection",
+ "items" => [Utils.generate_object_id(), Utils.generate_object_id()]
+ }
- Repo.delete(activity.object)
- Repo.delete(activity)
+ activity =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+ |> Kernel.put_in(["object", "replies"], replies)
- %{federation_output: federation_output, replies_uris: replies_uris}
+ %{activity: activity}
end
test "schedules background fetching of `replies` items if max thread depth limit allows", %{
- federation_output: federation_output,
- replies_uris: replies_uris
+ activity: activity
} do
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
+ clear_config([:instance, :federation_incoming_replies_max_depth], 1)
- {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
+ assert {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(activity)
+ object = Object.normalize(data["object"])
- for id <- replies_uris do
+ 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",
- %{federation_output: federation_output} do
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
+ %{activity: activity} do
+ clear_config([:instance, :federation_incoming_replies_max_depth], 0)
- {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
+ {:ok, _activity} = Transmogrifier.handle_incoming(activity)
assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
end
"object" => %{
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
+ "id" => Utils.generate_object_id(),
"type" => "Note",
"content" => "Hi",
"inReplyTo" => nil,
"object" => %{
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
+ "id" => Utils.generate_object_id(),
"type" => "Note",
"content" => "Hi",
"inReplyTo" => nil,
setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
setup do
- data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
[data: data]
end
end
test "returns object with inReplyTo when denied incoming reply", %{data: data} do
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
+ clear_config([:instance, :federation_incoming_replies_max_depth], 0)
object_with_reply =
Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
"https://mstdn.io/users/mayuutann/statuses/99568293732299394"
)
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
+ clear_config([:instance, :federation_incoming_replies_max_depth], 5)
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
assert modified_object["inReplyTo"] ==
describe "fix_attachments/1" do
test "returns not modified object" do
- data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
assert Transmogrifier.fix_attachments(data) == data
end
describe "fix_emoji/1" do
test "returns not modified object when object not contains tags" do
- data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
assert Transmogrifier.fix_emoji(data) == data
end
setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
test "returns unmodified object if activity doesn't have self-replies" do
- data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
assert Transmogrifier.set_replies(data) == data
end
in_reply_to_status_id: id1
})
- object = Object.normalize(activity)
+ 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} =
}
]
end
+
+ describe "fix_quote_url/1" do
+ test "a misskey quote should work", _ do
+ Tesla.Mock.mock(fn %{
+ method: :get,
+ url: "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924"
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/quoted_status.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+ end)
+
+ insert(:user, %{ap_id: "https://misskey.io/users/93492q0ip0"})
+ insert(:user, %{ap_id: "https://example.com/users/user"})
+
+ note =
+ "test/fixtures/misskey/quote.json"
+ |> File.read!()
+ |> Jason.decode!()
+
+ %{"quoteUri" => "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924"} =
+ Transmogrifier.fix_quote_url(note)
+ end
+
+ test "a fedibird quote should work", _ do
+ Tesla.Mock.mock(fn %{
+ method: :get,
+ url: "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924"
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/quoted_status.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+ end)
+
+ insert(:user, %{ap_id: "https://fedibird.com/users/akkoma_ap_integration_tester"})
+ insert(:user, %{ap_id: "https://example.com/users/user"})
+
+ note =
+ "test/fixtures/fedibird/quote.json"
+ |> File.read!()
+ |> Jason.decode!()
+
+ %{
+ "quoteUri" => "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924"
+ } = Transmogrifier.fix_quote_url(note)
+ end
+
+ test "quote fetching should stop after n levels", _ do
+ clear_config([:instance, :federation_incoming_replies_max_depth], 1)
+
+ Tesla.Mock.mock(fn %{
+ method: :get,
+ url: "https://misskey.io/notes/934gok3482"
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/misskey/recursive_quote.json"),
+ headers: HttpRequestMock.activitypub_object_headers()
+ }
+ end)
+
+ insert(:user, %{ap_id: "https://misskey.io/users/93492q0ip0"})
+
+ note =
+ "test/fixtures/misskey/recursive_quote.json"
+ |> File.read!()
+ |> Jason.decode!()
+
+ %{
+ "quoteUri" => "https://misskey.io/notes/934gok3482"
+ } = Transmogrifier.fix_quote_url(note)
+ end
+ end
end