
Current AP spec demands anonymous objects to have an id value,
but explicitly set it to JSON null. Howeveras it turns out this is
incompatible with JSON-LD requiring `@id` to be a string and thus AP
spec is incompatible iwth the Ativity Streams spec it is based on.
This is an issue for (the few) AP implementers actually performing
JSON-LD processing, like IceShrimp.NET.
This was uncovered by IceShrimp.NET’s zotan due to our adoption of
anonymous objects for emoj in f101886709
.
The issues is being discussed by W3C, and will most likely be resolved
via an errata redefining anonymous objects to completely omit the id
field just like transient objects already do. See:
https://github.com/w3c/activitypub/issues/476
Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/848
822 lines
28 KiB
Elixir
822 lines
28 KiB
Elixir
# Pleroma: A lightweight social networking server
|
|
# Copyright © 2017-2021 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, async: false
|
|
@moduletag :mocked
|
|
|
|
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
|
|
|
|
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")
|
|
|> Jason.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")
|
|
|> Jason.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")
|
|
|> Jason.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")
|
|
|> Jason.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
|
|
|
|
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"},
|
|
"name" => ":firefox:",
|
|
"type" => "Emoji",
|
|
"updated" => "1970-01-01T00:00:00Z"
|
|
}
|
|
]
|
|
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
|
|
|
|
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
|