Merge branch 'develop' into paradedb-search-backend

This commit is contained in:
lain 2026-07-26 09:15:42 +00:00
commit 3c916891f6
39 changed files with 1782 additions and 103 deletions

View file

@ -0,0 +1,32 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/attachment-embedded-note",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:01Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-attachment-embedded-note",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:01Z",
"content": "Note with embedded Note attachment",
"attachment": {
"type": "Note",
"attributedTo": "https://fedidev.fun/users/alice",
"name": "attached note",
"content": "This is just a note",
"published": "2024-03-06T07:23:56Z"
}
}
}

View file

@ -0,0 +1,31 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/attachment-payto-link",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:00Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-attachment-payto-link",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:00Z",
"content": "Payment Link",
"attachment": {
"type": "Link",
"name": "Donate",
"href": "payto://iban/DE75512108001245126199",
"rel": "payment"
}
}
}

View file

@ -0,0 +1,40 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/attachment-url-list",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:02Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-attachment-url-list",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:02Z",
"content": "url is array of two Link objects",
"attachment": {
"type": "Image",
"url": [
{
"type": "Link",
"href": "https://fedidev.fun/images/007.png",
"mediaType": "image/png"
},
{
"type": "Link",
"href": "https://fedidev.fun/images/008.jpg",
"mediaType": "image/jpeg"
}
]
}
}
}

View file

@ -0,0 +1,32 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/emoji-icon-string",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:00Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-emoji-icon-string",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:00Z",
"content": "emoji icon as string :cow:",
"tag": [
{
"type": "Emoji",
"name": ":cow:",
"icon": "https://fedidev.fun/static/cow.png"
}
]
}
}

View file

@ -0,0 +1,34 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/emoji-icon-url-missing-type",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:01Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-emoji-icon-url-missing-type",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:01Z",
"content": "emoji icon url missing type :cow2:",
"tag": [
{
"type": "Emoji",
"name": ":cow2:",
"icon": {
"url": "https://fedidev.fun/static/cow.png"
}
}
]
}
}

View file

@ -0,0 +1,31 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/emoji-missing-icon",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:02Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-emoji-missing-icon",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:02Z",
"content": "emoji missing icon :cow3:",
"tag": [
{
"type": "Emoji",
"name": ":cow3:"
}
]
}
}

View file

@ -0,0 +1,30 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/in-reply-to-collection",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:08Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-in-reply-to-collection",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:08Z",
"content": "collection",
"inReplyTo": {
"type": "Collection",
"items": [
"https://fedidev.fun/users/alice"
]
}
}
}

View file

@ -0,0 +1,35 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/in-reply-to-embedded-note",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:21Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-in-reply-to-embedded-note",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:21Z",
"content": "embedded note",
"inReplyTo": {
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"id": "https://fedidev.fun/assets/note1.jsonap",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"attributedTo": "https://fedidev.fun/users/alice",
"content": "One",
"published": "2024-01-06T13:11:45Z"
}
}
}

View file

@ -0,0 +1,28 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/in-reply-to-link",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:39:56Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-in-reply-to-link",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:39:56Z",
"content": "Link in inReplyTo",
"inReplyTo": {
"type": "Link",
"href": "https://fedidev.fun/assets/note1.jsonap"
}
}
}

View file

@ -0,0 +1,28 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/in-reply-to-list",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:08Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-in-reply-to-list",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:40:08Z",
"content": "two elements in inReplyTo",
"inReplyTo": [
"https://fedidev.fun/assets/note1.jsonap",
"https://fedidev.fun/assets/note2.jsonap"
]
}
}

View file

@ -0,0 +1,25 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/missing-attributedto",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:42:00Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-missing-attributedto",
"attributedTo": null,
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:42:00Z",
"content": "object without attributedTo"
}
}

View file

@ -0,0 +1,28 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/misskey-quote-list",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:53Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-misskey-quote-list",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:53Z",
"content": "_misskey_quote attribute as list",
"_misskey_quote": [
"https://fedidev.fun/assets/note1.jsonap",
"https://fedidev.fun/assets/note2.jsonap"
]
}
}

View file

@ -0,0 +1,28 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/object-type-list",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:32Z",
"object": {
"type": [
"Note"
],
"id": "https://fedidev.fun/objects/note-object-type-list",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:32Z",
"name": "name",
"summary": "summary",
"content": "content"
}
}

View file

@ -0,0 +1,28 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/quote-uri-list",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:52Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-quote-uri-list",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:52Z",
"content": "quoteUri attribute as list",
"quoteUri": [
"https://fedidev.fun/assets/note1.jsonap",
"https://fedidev.fun/assets/note2.jsonap"
]
}
}

View file

@ -0,0 +1,28 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/quote-url-list",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:52Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-quote-url-list",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:52Z",
"content": "quoteUrl attribute as list",
"quoteUrl": [
"https://fedidev.fun/assets/note1.jsonap",
"https://fedidev.fun/assets/note2.jsonap"
]
}
}

View file

@ -0,0 +1,30 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/tag-hashtag-missing-type",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:41:00Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-tag-hashtag-missing-type",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:41:00Z",
"content": "hashtag without type",
"tag": [
{
"name": "#Test"
}
]
}
}

View file

@ -0,0 +1,31 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/tag-mention-missing-type",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:41:01Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-tag-mention-missing-type",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:41:01Z",
"content": "mention without type",
"tag": [
{
"name": "@bob",
"href": "https://remote.example/users/bob"
}
]
}
}

27
test/fixtures/fedidev.fun/url_list.json vendored Normal file
View file

@ -0,0 +1,27 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/url-list",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:39:06Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-url-list",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:39:06Z",
"content": "text",
"url": [
"https://fedidev.fun/objects/123"
]
}
}

View file

@ -27,6 +27,11 @@ defmodule Pleroma.Object.ContainmentTest do
})
end
test "rejects an empty actor list without crashing" do
assert :error ==
Containment.contain_origin("https://example.com/objects/1", %{"actor" => []})
end
test "contain_origin_from_id() catches obvious spoofing attempts" do
data = %{
"id" => "http://example.com/~alyssa/activities/1234.json"

View file

@ -729,6 +729,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert Activity.get_by_ap_id(data["id"])
end
test "rejects duplicate Signature headers before processing signed inbox requests", %{
conn: conn
} do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
conn =
conn
|> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json")
|> Map.update!(:req_headers, &[{"signature", "sig1=:fake-signature:"} | &1])
|> post("/inbox", data)
assert "error, unsupported HTTP Message Signature" == json_response(conn, 401)
assert [] == all_enqueued(worker: ReceiverWorker)
end
test "it inserts an incoming activity into the database" <>
"even if we can't fetch the user but have it in our db",
%{conn: conn} do
@ -757,6 +773,120 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert Activity.get_by_ap_id(data["id"])
end
test "rejects unsupported HTTP Message Signatures so clients can fall back", %{conn: conn} do
data = %{
"type" => "Create",
"actor" => "https://activitypubbot.example/user/ok",
"id" => "https://activitypubbot.example/activities/rfc9421-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => "https://activitypubbot.example/objects/rfc9421-note",
"actor" => "https://activitypubbot.example/user/ok",
"attributedTo" => "https://activitypubbot.example/user/ok",
"content" => "hello from activitypub-bot",
"published" => "2026-05-29T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> false end)
conn =
conn
|> put_req_header("content-type", "application/activity+json")
|> put_req_header("date", "Fri, 29 May 2026 13:33:31 GMT")
|> put_req_header("content-digest", "sha-256=:fake-digest:")
|> put_req_header(
"signature-input",
"sig1=(\"@method\" \"@target-uri\" \"date\" \"content-type\" \"content-digest\");keyid=\"https://activitypubbot.example/user/ok/publickey\";alg=\"rsa-v1_5-sha256\";created=1780061611"
)
|> put_req_header("signature", "sig1=:fake-signature:")
|> post("/inbox", data)
assert "error, unsupported HTTP Message Signature" == json_response(conn, 401)
assert [] == all_enqueued(worker: SignatureRetryWorker)
end
test "rejects duplicate Signature headers so clients can fall back", %{conn: conn} do
data = %{
"type" => "Create",
"actor" => "https://activitypubbot.example/user/ok",
"id" => "https://activitypubbot.example/activities/duplicate-signature-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => "https://activitypubbot.example/objects/duplicate-signature-note",
"actor" => "https://activitypubbot.example/user/ok",
"attributedTo" => "https://activitypubbot.example/user/ok",
"content" => "hello from activitypub-bot",
"published" => "2026-05-29T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
legacy_signature =
"keyId=\"https://activitypubbot.example/user/ok#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> false end)
conn =
conn
|> put_req_header("content-type", "application/activity+json")
|> put_req_header("date", "Fri, 29 May 2026 13:33:31 GMT")
|> put_req_header("digest", "SHA-256=fake-digest")
|> put_req_header("signature", "sig1=:fake-signature:")
|> Map.update!(:req_headers, &[{"signature", legacy_signature} | &1])
|> post("/inbox", data)
assert "error, unsupported HTTP Message Signature" == json_response(conn, 401)
assert [] == all_enqueued(worker: SignatureRetryWorker)
end
test "keeps failed-signature retry for legacy signatures with Signature-Input", %{conn: conn} do
data = %{
"type" => "Create",
"actor" => "https://activitypubbot.example/user/ok",
"id" => "https://activitypubbot.example/activities/legacy-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => "https://activitypubbot.example/objects/legacy-note",
"actor" => "https://activitypubbot.example/user/ok",
"attributedTo" => "https://activitypubbot.example/user/ok",
"content" => "hello from activitypub-bot",
"published" => "2026-05-29T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> false end)
conn =
conn
|> put_req_header("content-type", "application/activity+json")
|> put_req_header("date", "Fri, 29 May 2026 13:33:31 GMT")
|> put_req_header("digest", "SHA-256=fake-digest")
|> put_req_header(
"signature-input",
"sig1=(\"@method\" \"@target-uri\" \"date\" \"content-type\" \"digest\");keyid=\"https://activitypubbot.example/user/ok/publickey\";alg=\"rsa-v1_5-sha256\";created=1780061611"
)
|> put_req_header(
"signature",
"keyId=\"https://activitypubbot.example/user/ok#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
)
|> post("/inbox", data)
assert "ok" == json_response(conn, 200)
assert [_] = all_enqueued(worker: SignatureRetryWorker)
end
test "does not create a forged post after failed signature retry", %{conn: conn} do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
@ -1320,6 +1450,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert Activity.get_by_ap_id(data["id"])
end
test "rejects duplicate Signature headers", %{conn: conn, data: data} do
user = insert(:user)
conn =
conn
|> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json")
|> Map.update!(:req_headers, &[{"signature", "sig1=:fake-signature:"} | &1])
|> post("/users/#{user.nickname}/inbox", data)
assert "error, unsupported HTTP Message Signature" == json_response(conn, 401)
assert [] == all_enqueued(worker: ReceiverWorker)
end
test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
user = insert(:user)

View file

@ -0,0 +1,98 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunAttachmentsTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Object
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
defp ensure_remote_actor!(ap_id) when is_binary(ap_id) do
case User.get_cached_by_ap_id(ap_id) do
%User{} ->
:ok
_ ->
insert(:user,
local: false,
ap_id: ap_id,
follower_address: ap_id <> "/followers",
following_address: ap_id <> "/following",
featured_address: ap_id <> "/collections/featured",
inbox: ap_id <> "/inbox"
)
:ok
end
end
defp ingest_data!(data) do
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
{activity, object}
end
defp ingest_fixture!(rel_path) do
rel_path
|> File.read!()
|> Jason.decode!()
|> ingest_data!()
end
test "drops non-http(s) attachment href" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/attachment_payto_link.json")
assert object.data["attachment"] == []
end
test "drops embedded non-attachment objects" do
{_activity, object} =
ingest_fixture!("test/fixtures/fedidev.fun/attachment_embedded_note.json")
assert object.data["attachment"] == []
end
test "accepts attachment url as list and keeps the first link" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/attachment_url_list.json")
[attachment] = object.data["attachment"]
[%{"href" => href} | _] = attachment["url"]
assert href == "https://fedidev.fun/images/007.png"
end
test "drops malformed attachment list entries" do
data = File.read!("test/fixtures/fedidev.fun/attachment_url_list.json") |> Jason.decode!()
data = update_in(data, ["object", "attachment"], &["malformed" | List.wrap(&1)])
{_activity, object} = ingest_data!(data)
assert length(object.data["attachment"]) == 1
end
test "accepts a URI string in an attachment url list" do
data = File.read!("test/fixtures/fedidev.fun/attachment_url_list.json") |> Jason.decode!()
data =
put_in(data, ["object", "attachment", "url"], [
"https://fedidev.fun/images/007.png"
])
{_activity, object} = ingest_data!(data)
assert [%{"url" => [%{"href" => "https://fedidev.fun/images/007.png"}]}] =
object.data["attachment"]
end
end

View file

@ -0,0 +1,103 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunEmojiTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Object
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
defp ensure_remote_actor!(ap_id) when is_binary(ap_id) do
case User.get_cached_by_ap_id(ap_id) do
%User{} ->
:ok
_ ->
insert(:user,
local: false,
ap_id: ap_id,
follower_address: ap_id <> "/followers",
following_address: ap_id <> "/following",
featured_address: ap_id <> "/collections/featured",
inbox: ap_id <> "/inbox"
)
:ok
end
end
defp ingest_data!(data) do
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
{activity, object}
end
defp ingest_fixture!(rel_path) do
rel_path
|> File.read!()
|> Jason.decode!()
|> ingest_data!()
end
test "ingests Emoji tag with string icon" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/emoji_icon_string.json")
assert object.data["emoji"]["cow"] == "https://fedidev.fun/static/cow.png"
end
test "ingests Emoji tag with icon missing type" do
{_activity, object} =
ingest_fixture!("test/fixtures/fedidev.fun/emoji_icon_url_missing_type.json")
assert object.data["emoji"]["cow2"] == "https://fedidev.fun/static/cow.png"
end
test "ignores Emoji tag missing icon" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/emoji_missing_icon.json")
assert object.data["emoji"] == %{}
end
test "ignores Emoji tag missing a string name" do
data = File.read!("test/fixtures/fedidev.fun/emoji_icon_string.json") |> Jason.decode!()
data = put_in(data, ["object", "tag", Access.at(0), "name"], nil)
{_activity, object} = ingest_data!(data)
assert object.data["emoji"] == %{}
end
test "ignores Emoji tag with a non-http icon" do
data = File.read!("test/fixtures/fedidev.fun/emoji_icon_string.json") |> Jason.decode!()
data = put_in(data, ["object", "tag", Access.at(0), "icon"], "file:///tmp/cow.png")
{_activity, object} = ingest_data!(data)
assert object.data["emoji"] == %{}
end
test "normalizes a list-typed Emoji tag before building the emoji map" do
data = File.read!("test/fixtures/fedidev.fun/emoji_icon_string.json") |> Jason.decode!()
data =
put_in(data, ["object", "tag", Access.at(0), "type"], [
"https://example.com/ns#CustomTag",
"Emoji"
])
{_activity, object} = ingest_data!(data)
assert object.data["emoji"]["cow"] == "https://fedidev.fun/static/cow.png"
end
end

View file

@ -0,0 +1,72 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunInReplyToTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Object
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
defp ensure_remote_actor!(ap_id) when is_binary(ap_id) do
case User.get_cached_by_ap_id(ap_id) do
%User{} ->
:ok
_ ->
insert(:user,
local: false,
ap_id: ap_id,
follower_address: ap_id <> "/followers",
following_address: ap_id <> "/following",
featured_address: ap_id <> "/collections/featured",
inbox: ap_id <> "/inbox"
)
:ok
end
end
defp ingest_fixture!(rel_path) do
data = rel_path |> File.read!() |> Jason.decode!()
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
{activity, object}
end
test "normalizes Link inReplyTo to a string" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/in_reply_to_link.json")
assert object.data["inReplyTo"] == "https://fedidev.fun/assets/note1.jsonap"
end
test "normalizes list inReplyTo to the first object ID" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/in_reply_to_list.json")
assert object.data["inReplyTo"] == "https://fedidev.fun/assets/note1.jsonap"
end
test "drops invalid map shapes in inReplyTo" do
{_activity, object} =
ingest_fixture!("test/fixtures/fedidev.fun/in_reply_to_collection.json")
assert object.data["inReplyTo"] == nil
end
test "accepts embedded objects in inReplyTo" do
{_activity, object} =
ingest_fixture!("test/fixtures/fedidev.fun/in_reply_to_embedded_note.json")
assert object.data["inReplyTo"] == "https://fedidev.fun/assets/note1.jsonap"
end
end

View file

@ -0,0 +1,49 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunMissingAttributedToTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Object
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
defp ensure_remote_actor!(ap_id) when is_binary(ap_id) do
case User.get_cached_by_ap_id(ap_id) do
%User{} ->
:ok
_ ->
insert(:user,
local: false,
ap_id: ap_id,
follower_address: ap_id <> "/followers",
following_address: ap_id <> "/following",
featured_address: ap_id <> "/collections/featured",
inbox: ap_id <> "/inbox"
)
:ok
end
end
test "inherits attributedTo from surrounding activity" do
data = File.read!("test/fixtures/fedidev.fun/missing_attributed_to.json") |> Jason.decode!()
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
assert object.data["attributedTo"] == data["actor"]
assert object.data["actor"] == data["actor"]
end
end

View file

@ -0,0 +1,64 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunObjectTypeListTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Object
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
defp ensure_remote_actor!(ap_id) when is_binary(ap_id) do
case User.get_cached_by_ap_id(ap_id) do
%User{} ->
:ok
_ ->
insert(:user,
local: false,
ap_id: ap_id,
follower_address: ap_id <> "/followers",
following_address: ap_id <> "/following",
featured_address: ap_id <> "/collections/featured",
inbox: ap_id <> "/inbox"
)
:ok
end
end
defp ingest_fixture!(rel_path) do
data = rel_path |> File.read!() |> Jason.decode!()
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
{activity, object}
end
test "accepts object type as a list and stores the first type" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/object_type_list.json")
assert object.data["type"] == "Note"
end
test "selects a supported type after an extension type" do
data = File.read!("test/fixtures/fedidev.fun/object_type_list.json") |> Jason.decode!()
data = put_in(data, ["object", "type"], ["https://example.com/ns#CustomType", "Note"])
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
assert object.data["type"] == "Note"
end
end

View file

@ -0,0 +1,63 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunQuoteUrlListTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Object
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
defp ensure_remote_actor!(ap_id) when is_binary(ap_id) do
case User.get_cached_by_ap_id(ap_id) do
%User{} ->
:ok
_ ->
insert(:user,
local: false,
ap_id: ap_id,
follower_address: ap_id <> "/followers",
following_address: ap_id <> "/following",
featured_address: ap_id <> "/collections/featured",
inbox: ap_id <> "/inbox"
)
:ok
end
end
defp ingest_fixture!(rel_path) do
data = rel_path |> File.read!() |> Jason.decode!()
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
{activity, object}
end
test "normalizes quoteUri list to the first object ID" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/quote_uri_list.json")
assert object.data["quoteUrl"] == "https://fedidev.fun/assets/note1.jsonap"
end
test "normalizes quoteUrl list to the first object ID" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/quote_url_list.json")
assert object.data["quoteUrl"] == "https://fedidev.fun/assets/note1.jsonap"
end
test "normalizes _misskey_quote list to the first object ID" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/misskey_quote_list.json")
assert object.data["quoteUrl"] == "https://fedidev.fun/assets/note1.jsonap"
end
end

View file

@ -0,0 +1,112 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunTagInferenceTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Object
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
defp ensure_remote_actor!(ap_id) when is_binary(ap_id) do
case User.get_cached_by_ap_id(ap_id) do
%User{} ->
:ok
_ ->
insert(:user,
local: false,
ap_id: ap_id,
follower_address: ap_id <> "/followers",
following_address: ap_id <> "/following",
featured_address: ap_id <> "/collections/featured",
inbox: ap_id <> "/inbox"
)
:ok
end
end
defp ingest_fixture!(rel_path) do
data = rel_path |> File.read!() |> Jason.decode!()
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
{activity, object}
end
test "infers Hashtag type when missing" do
{_activity, object} =
ingest_fixture!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json")
assert "test" in Object.hashtags(object)
end
test "infers Mention type when missing" do
{_activity, object} =
ingest_fixture!("test/fixtures/fedidev.fun/tag_mention_missing_type.json")
assert Enum.any?(object.data["tag"], fn
%{"type" => "Mention", "href" => "https://remote.example/users/bob"} -> true
_ -> false
end)
end
test "normalizes a list-typed tag type" do
data =
File.read!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json") |> Jason.decode!()
data = put_in(data, ["object", "tag", Access.at(0), "type"], ["Hashtag"])
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
assert "test" in Object.hashtags(object)
end
test "ignores a list-typed Hashtag without a string name" do
data =
File.read!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json") |> Jason.decode!()
data = put_in(data, ["object", "tag", Access.at(0), "type"], ["Hashtag"])
data = put_in(data, ["object", "tag", Access.at(0), "name"], nil)
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
assert Object.hashtags(object) == []
end
test "infers a Link before deriving quoteUrl" do
data =
File.read!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json") |> Jason.decode!()
data =
put_in(data, ["object", "tag"], [
%{
"href" => "https://fedidev.fun/assets/note1.jsonap",
"mediaType" => "application/activity+json"
}
])
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
assert object.data["quoteUrl"] == "https://fedidev.fun/assets/note1.jsonap"
end
end

View file

@ -0,0 +1,53 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunUrlListTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Object
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
defp ensure_remote_actor!(ap_id) when is_binary(ap_id) do
case User.get_cached_by_ap_id(ap_id) do
%User{} ->
:ok
_ ->
insert(:user,
local: false,
ap_id: ap_id,
follower_address: ap_id <> "/followers",
following_address: ap_id <> "/following",
featured_address: ap_id <> "/collections/featured",
inbox: ap_id <> "/inbox"
)
:ok
end
end
defp ingest_fixture!(rel_path) do
data = rel_path |> File.read!() |> Jason.decode!()
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
{activity, object}
end
test "normalizes object url list to a string" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/url_list.json")
assert object.data["url"] == "https://fedidev.fun/objects/123"
end
end

View file

@ -572,11 +572,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
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"]
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"] == []
assert modified_object["inReplyTo"] == nil
end
test "returns modified object when allowed incoming reply", %{data: data} do
@ -671,13 +671,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
test "returns object with emoji when object contains list tags" do
assert Transmogrifier.fix_emoji(%{
"tag" => [
%{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
%{
"type" => "Emoji",
"name" => ":bib:",
"icon" => %{"url" => "https://example.com/test"}
},
%{"type" => "Hashtag"}
]
}) == %{
"emoji" => %{"bib" => "/test"},
"emoji" => %{"bib" => "https://example.com/test"},
"tag" => [
%{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
%{
"icon" => %{"url" => "https://example.com/test"},
"name" => ":bib:",
"type" => "Emoji"
},
%{"type" => "Hashtag"}
]
}
@ -685,10 +693,28 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
test "returns object with emoji when object contains map tag" do
assert Transmogrifier.fix_emoji(%{
"tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
"tag" => %{
"type" => "Emoji",
"name" => ":bib:",
"icon" => %{"url" => "https://example.com/test"}
}
}) == %{
"emoji" => %{"bib" => "/test"},
"tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
"emoji" => %{"bib" => "https://example.com/test"},
"tag" => %{
"icon" => %{"url" => "https://example.com/test"},
"name" => ":bib:",
"type" => "Emoji"
}
}
end
end
describe "fix_tag/1" do
test "ignores malformed Hashtag names and tag entries" do
tag = %{"type" => "Hashtag", "name" => 123}
assert Transmogrifier.fix_tag(%{"tag" => [tag, "malformed"]}) == %{
"tag" => [tag, "malformed"]
}
end
end

View file

@ -50,6 +50,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.UserUpdateHandlingTest do
assert user.bio == "<p>Some bio</p>"
end
test "it selects an actor type after an extension type" do
user = insert(:user, local: false)
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
object =
update_data["object"]
|> Map.put("actor", user.ap_id)
|> Map.put("id", user.ap_id)
|> Map.put("type", ["https://example.com/ns#CustomActor", "Person"])
update_data =
update_data
|> Map.put("actor", user.ap_id)
|> Map.put("object", object)
assert {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(update_data)
assert User.get_cached_by_ap_id(user.ap_id).name == "gargle"
end
test "it works with alsoKnownAs" do
%{ap_id: actor} = insert(:user, local: false)