From 1419eee5dfe1f3d76c28ab7c6f3cb24ba652fef2 Mon Sep 17 00:00:00 2001
From: floatingghost
Date: Mon, 25 Jul 2022 16:30:06 +0000
Subject: [PATCH] Quote posting (#113)
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/113
---
CHANGELOG.md | 1 +
config/config.exs | 2 +
docs/docs/administration/CLI_tasks/user.md | 25 +++++
lib/pleroma/activity.ex | 6 ++
lib/pleroma/web/activity_pub/builder.ex | 11 +++
.../activity_pub/mrf/inline_quote_policy.ex | 71 ++++++++++++++
.../article_note_page_validator.ex | 1 +
.../object_validators/common_fields.ex | 1 +
.../web/activity_pub/transmogrifier.ex | 44 +++++++++
.../api_spec/operations/status_operation.ex | 5 +
lib/pleroma/web/api_spec/schemas/status.ex | 37 +++++++
lib/pleroma/web/common_api.ex | 4 +
lib/pleroma/web/common_api/activity_draft.ex | 25 +++++
.../web/mastodon_api/views/status_view.ex | 19 ++++
priv/static/schemas/litepub-0.1.jsonld | 4 +
test/fixtures/fedibird/quote.json | 73 ++++++++++++++
test/fixtures/misskey/quote.json | 50 ++++++++++
.../quote_post/fedibird_quote_post.json | 52 ++++++++++
.../quote_post/fedibird_quote_uri.json | 54 ++++++++++
.../quote_post/misskey_quote_post.json | 46 +++++++++
test/fixtures/quoted_status.json | 38 +++++++
.../pleroma/web/activity_pub/builder_test.exs | 7 +-
.../mrf/inline_quote_policy_test.exs | 56 +++++++++++
.../article_note_page_validator_test.exs | 56 +++++++++++
.../controllers/filter_controller_test.exs | 12 ++-
.../controllers/status_controller_test.exs | 98 +++++++++++++++++++
.../mastodon_api/views/status_view_test.exs | 28 +++++-
27 files changed, 819 insertions(+), 7 deletions(-)
create mode 100644 lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex
create mode 100644 test/fixtures/fedibird/quote.json
create mode 100644 test/fixtures/misskey/quote.json
create mode 100644 test/fixtures/quote_post/fedibird_quote_post.json
create mode 100644 test/fixtures/quote_post/fedibird_quote_uri.json
create mode 100644 test/fixtures/quote_post/misskey_quote_post.json
create mode 100644 test/fixtures/quoted_status.json
create mode 100644 test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6dd4dd4c..4982a3191 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
- extended runtime module support, see config cheatsheet
+- quote posting; quotes are limited to public posts
### Fixed
- Updated mastoFE path, for the newer version
diff --git a/config/config.exs b/config/config.exs
index cfe207dcc..bac167c29 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -407,6 +407,8 @@ config :pleroma, :mrf_vocabulary,
accept: [],
reject: []
+config :pleroma, :mrf_inline_quote, prefix: "RE"
+
# threshold of 7 days
config :pleroma, :mrf_object_age,
threshold: 604_800,
diff --git a/docs/docs/administration/CLI_tasks/user.md b/docs/docs/administration/CLI_tasks/user.md
index 0d19b5622..b7a60751d 100644
--- a/docs/docs/administration/CLI_tasks/user.md
+++ b/docs/docs/administration/CLI_tasks/user.md
@@ -300,3 +300,28 @@
```sh
mix pleroma.user unconfirm_all
```
+
+## Fix following state
+
+Sometimes the system can get into a situation where
+it think you're already following someone and won't send a request
+to the remote instance, or won't let you unfollow someone. This
+bug was fixed, but in case you encounter these weird states:
+
+=== "OTP"
+
+ ```sh
+ ./bin/pleroma_ctl user fix_follow_state localuser remoteuser@example.com
+ ```
+
+=== "From Source"
+
+ ```sh
+ mix pleroma.user fix_follow_state localuser remoteuser@example.com
+ ```
+
+The first argument is the local user's nickname - if you are `myuser@myinstance`, this should be `myuser`.
+
+The second is the remote user, consisting of both nickname AND domain.
+
+If you are a weird follow state situation and cannot resolve it with the above, you may need to co-operate with the remote admin to clear the state their side too - they should provide the arguments *backwards*, i.e `fix_follow_state remote local`.
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index abfe778d2..01c9df53b 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -292,6 +292,12 @@ defmodule Pleroma.Activity do
get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))
end
+ def get_quoted_activity_from_object(%Object{data: %{"quoteUri" => ap_id}}) do
+ get_create_by_object_ap_id_with_object(ap_id)
+ end
+
+ def get_quoted_activity_from_object(_), do: nil
+
def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id)
def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id)
def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index 8c51b8d63..97ceaf08e 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -168,6 +168,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
}
|> add_in_reply_to(draft.in_reply_to)
+ |> add_quote(draft.quote)
|> Map.merge(draft.extra)
{:ok, data, []}
@@ -183,6 +184,16 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
end
+ defp add_quote(object, nil), do: object
+
+ defp add_quote(object, quote) do
+ with %Object{} = quote_object <- Object.normalize(quote, fetch: false) do
+ Map.put(object, "quoteUri", quote_object.data["id"])
+ else
+ _ -> object
+ end
+ end
+
def answer(user, object, name) do
{:ok,
%{
diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex
new file mode 100644
index 000000000..20432410b
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex
@@ -0,0 +1,71 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
+ @moduledoc "Force a quote line into the message content."
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ defp build_inline_quote(prefix, url) do
+ "
#{prefix}: #{url}"
+ end
+
+ defp has_inline_quote?(content, quote_url) do
+ cond do
+ # Does the quote URL exist in the content?
+ content =~ quote_url -> true
+ # Does the content already have a .quote-inline span?
+ content =~ "" -> true
+ # No inline quote found
+ true -> false
+ end
+ end
+
+ defp filter_object(%{"quoteUri" => quote_url} = object) do
+ content = object["content"] || ""
+
+ if has_inline_quote?(content, quote_url) do
+ object
+ else
+ prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix])
+
+ content =
+ if String.ends_with?(content, "
") do
+ String.trim_trailing(content, "") <> build_inline_quote(prefix, quote_url) <> ""
+ else
+ content <> build_inline_quote(prefix, quote_url)
+ end
+
+ Map.put(object, "content", content)
+ end
+ end
+
+ @impl true
+ def filter(%{"object" => %{"quoteUri" => _} = object} = activity) do
+ {:ok, Map.put(activity, "object", filter_object(object))}
+ end
+
+ @impl true
+ def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_inline_quote,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy",
+ label: "MRF Inline Quote",
+ description: "Force quote post URLs inline",
+ children: [
+ %{
+ key: :prefix,
+ type: :string,
+ description: "Prefix before the link",
+ suggestions: ["RE", "QT", "RT", "RN"]
+ }
+ ]
+ }
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
index 5e377c294..a0724ca55 100644
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
@@ -156,6 +156,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|> fix_replies()
|> fix_source()
|> fix_misskey_content()
+ |> Transmogrifier.fix_quote_url()
|> Transmogrifier.fix_attachments()
|> Transmogrifier.fix_emoji()
|> Transmogrifier.fix_content_map()
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
index 37ec860dc..1eaf572b9 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
@@ -59,6 +59,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:quoteUri, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 115dfc470..b6ee24ee6 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -598,6 +598,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def set_reply_to_uri(obj), do: obj
+ def set_quote_url(%{"quoteUri" => quote} = object) when is_binary(quote) do
+ Map.put(object, "quoteUrl", quote)
+ end
+
+ def set_quote_url(obj), do: obj
+
@doc """
Serialized Mastodon-compatible `replies` collection containing _self-replies_.
Based on Mastodon's ActivityPub::NoteSerializer#replies.
@@ -652,6 +658,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> prepare_attachments
|> set_conversation
|> set_reply_to_uri
+ |> set_quote_url()
|> set_replies
|> strip_internal_fields
|> strip_internal_tags
@@ -879,6 +886,43 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp strip_internal_tags(object), do: object
+ def fix_quote_url(object, options \\ [])
+
+ def fix_quote_url(%{"quoteUri" => quote_url} = object, options)
+ when not is_nil(quote_url) do
+ with {:ok, quoted_object} <- get_obj_helper(quote_url, options),
+ %Activity{} <- Activity.get_create_by_object_ap_id(quoted_object.data["id"]) do
+ Map.put(object, "quoteUri", quoted_object.data["id"])
+ else
+ e ->
+ Logger.warn("Couldn't fetch #{inspect(quote_url)}, error: #{inspect(e)}")
+ object
+ end
+ end
+
+ # Soapbox
+ def fix_quote_url(%{"quoteUrl" => quote_url} = object, options) do
+ object
+ |> Map.put("quoteUri", quote_url)
+ |> fix_quote_url(options)
+ end
+
+ # Old Fedibird (bug)
+ # https://github.com/fedibird/mastodon/issues/9
+ def fix_quote_url(%{"quoteURL" => quote_url} = object, options) do
+ object
+ |> Map.put("quoteUri", quote_url)
+ |> fix_quote_url(options)
+ end
+
+ def fix_quote_url(%{"_misskey_quote" => quote_url} = object, options) do
+ object
+ |> Map.put("quoteUri", quote_url)
+ |> fix_quote_url(options)
+ end
+
+ def fix_quote_url(object, _), do: object
+
def perform(:user_upgrade, user) do
# we pass a fake user so that the followers collection is stripped away
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex
index c7df676c3..a5da8b58e 100644
--- a/lib/pleroma/web/api_spec/operations/status_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/status_operation.ex
@@ -496,6 +496,11 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
type: :string,
description:
"Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`."
+ },
+ quote_id: %Schema{
+ nullable: true,
+ type: :string,
+ description: "Will quote a given status."
}
},
example: %{
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
index 3caab0f00..60db8ad6f 100644
--- a/lib/pleroma/web/api_spec/schemas/status.ex
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -133,6 +133,16 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
type: :boolean,
description: "Have you pinned this status? Only appears if the status is pinnable."
},
+ quote_id: %Schema{
+ type: :string,
+ description: "ID of the status being quoted",
+ nullable: true
+ },
+ quote: %Schema{
+ allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
+ nullable: true,
+ description: "Quoted status (if any)"
+ },
pleroma: %Schema{
type: :object,
properties: %{
@@ -204,6 +214,33 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
}
}
},
+ akkoma: %Schema{
+ type: :object,
+ properties: %{
+ source: %Schema{
+ nullable: true,
+ oneOf: [
+ %Schema{type: :string, example: 'plaintext content'},
+ %Schema{
+ type: :object,
+ properties: %{
+ content: %Schema{
+ type: :string,
+ description: "The source content of the status",
+ nullable: true
+ },
+ mediaType: %Schema{
+ type: :string,
+ description: "The source MIME type of the status",
+ example: "text/plain",
+ nullable: true
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
poll: %Schema{allOf: [Poll], nullable: true, description: "The poll attached to the status"},
reblog: %Schema{
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index 8ab50cf2b..bc5e26cf7 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -319,6 +319,10 @@ defmodule Pleroma.Web.CommonAPI do
end
end
+ def get_quoted_visibility(nil), do: nil
+
+ def get_quoted_visibility(activity), do: get_replied_to_visibility(activity)
+
def check_expiry_date({:ok, nil} = res), do: res
def check_expiry_date({:ok, in_seconds}) do
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index ea88213fb..767b2bf0f 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -22,6 +22,8 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
attachments: [],
in_reply_to: nil,
in_reply_to_conversation: nil,
+ quote_id: nil,
+ quote: nil,
visibility: nil,
expires_at: nil,
extra: nil,
@@ -54,6 +56,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|> with_valid(&in_reply_to/1)
|> with_valid(&in_reply_to_conversation/1)
|> with_valid(&visibility/1)
+ |> with_valid("e_id/1)
|> content()
|> with_valid(&to_and_cc/1)
|> with_valid(&context/1)
@@ -108,6 +111,28 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
end
+ defp quote_id(%{params: %{quote_id: ""}} = draft), do: draft
+
+ defp quote_id(%{params: %{quote_id: id}} = draft) when is_binary(id) do
+ with {:activity, %Activity{} = quote} <- {:activity, Activity.get_by_id(id)},
+ visibility <- CommonAPI.get_quoted_visibility(quote),
+ {:visibility, true} <- {:visibility, visibility in ["public", "unlisted"]} do
+ %__MODULE__{draft | quote: Activity.get_by_id(id)}
+ else
+ {:activity, _} ->
+ add_error(draft, dgettext("errors", "You can't quote a status that doesn't exist"))
+
+ {:visibility, false} ->
+ add_error(draft, dgettext("errors", "You can only quote public or unlisted statuses"))
+ end
+ end
+
+ defp quote_id(%{params: %{quote_id: %Activity{} = quote}} = draft) do
+ %__MODULE__{draft | quote: quote}
+ end
+
+ defp quote_id(draft), do: draft
+
defp visibility(%{params: params} = draft) do
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
{visibility, "direct"} when visibility != "direct" ->
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index c0f467592..cf4ea51e0 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -329,6 +329,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
{pinned?, pinned_at} = pin_data(object, user)
+ quote = Activity.get_quoted_activity_from_object(object)
+
%{
id: to_string(activity.id),
uri: object.data["id"],
@@ -363,6 +365,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
application: build_application(object.data["generator"]),
language: nil,
emojis: build_emojis(object.data["emoji"]),
+ quote_id: if(quote, do: quote.id, else: nil),
+ quote: maybe_render_quote(quote, opts),
pleroma: %{
local: activity.local,
conversation_id: get_context_id(activity),
@@ -604,4 +608,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
defp build_image_url(_, _), do: nil
+
+ defp maybe_render_quote(nil, _), do: nil
+
+ defp maybe_render_quote(quote, opts) do
+ if opts[:do_not_recurse] || !visible_for_user?(quote, opts[:for]) do
+ nil
+ else
+ opts =
+ opts
+ |> Map.put(:activity, quote)
+ |> Map.put(:do_not_recurse, true)
+
+ render("show.json", opts)
+ end
+ end
end
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index e7722cf72..d2b62ba77 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -17,6 +17,8 @@
"ostatus": "http://ostatus.org#",
"schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#",
+ "misskey": "https://misskey-hub.net/ns#",
+ "fedibird": "http://fedibird.com/ns#",
"value": "schema:value",
"sensitive": "as:sensitive",
"litepub": "http://litepub.social/ns#",
@@ -26,6 +28,8 @@
"@id": "litepub:listMessage",
"@type": "@id"
},
+ "quoteUrl": "as:quoteUrl",
+ "quoteUri": "fedibird:quoteUri",
"oauthRegistrationEndpoint": {
"@id": "litepub:oauthRegistrationEndpoint",
"@type": "@id"
diff --git a/test/fixtures/fedibird/quote.json b/test/fixtures/fedibird/quote.json
new file mode 100644
index 000000000..a43cc31e5
--- /dev/null
+++ b/test/fixtures/fedibird/quote.json
@@ -0,0 +1,73 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "ostatus": "http://ostatus.org#",
+ "atomUri": "ostatus:atomUri",
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "sensitive": "as:sensitive",
+ "toot": "http://joinmastodon.org/ns#",
+ "votersCount": "toot:votersCount",
+ "fedibird": "http://fedibird.com/ns#",
+ "quoteUri": "fedibird:quoteUri",
+ "expiry": "fedibird:expiry",
+ "references": {
+ "@id": "fedibird:references",
+ "@type": "@id"
+ },
+ "emojiReactions": {
+ "@id": "fedibird:emojiReactions",
+ "@type": "@id"
+ }
+ }
+ ],
+ "id": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674",
+ "type": "Note",
+ "summary": null,
+ "inReplyTo": null,
+ "published": "2022-07-25T11:12:26Z",
+ "url": "https://fedibird.com/@akkoma_ap_integration_tester/108707679228362674",
+ "attributedTo": "https://fedibird.com/users/akkoma_ap_integration_tester",
+ "to": [
+ "https://fedibird.com/users/akkoma_ap_integration_tester/followers"
+ ],
+ "cc": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "sensitive": false,
+ "atomUri": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674",
+ "inReplyToAtomUri": null,
+ "conversation": "tag:fedibird.com,2022-07-25:objectId=108707679228389900:objectType=Conversation",
+ "context": "https://fedibird.com/contexts/108707679228389900",
+ "quoteUri": "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924",
+ "_misskey_quote": "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924",
+ "_misskey_content": "public quote",
+ "content": "public quote
QT: https://example.com/objects/24d9f2e1-32d2-4bd5-bdf2-8ea61d3fb5e8 [åç
§]
",
+ "contentMap": {
+ "ja": "public quote
QT: https://example.com/objects/24d9f2e1-32d2-4bd5-bdf2-8ea61d3fb5e8 [åç
§]
"
+ },
+ "attachment": [],
+ "tag": [],
+ "replies": {
+ "id": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674/replies",
+ "type": "Collection",
+ "first": {
+ "type": "CollectionPage",
+ "next": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674/replies?only_other_accounts=true&page=true",
+ "partOf": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674/replies",
+ "items": []
+ }
+ },
+ "references": {
+ "id": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674/references",
+ "type": "Collection",
+ "first": {
+ "type": "CollectionPage",
+ "partOf": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674/references",
+ "items": [
+ "https://example.com/objects/24d9f2e1-32d2-4bd5-bdf2-8ea61d3fb5e8"
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/misskey/quote.json b/test/fixtures/misskey/quote.json
new file mode 100644
index 000000000..487461020
--- /dev/null
+++ b/test/fixtures/misskey/quote.json
@@ -0,0 +1,50 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "sensitive": "as:sensitive",
+ "Hashtag": "as:Hashtag",
+ "quoteUrl": "as:quoteUrl",
+ "toot": "http://joinmastodon.org/ns#",
+ "Emoji": "toot:Emoji",
+ "featured": "toot:featured",
+ "discoverable": "toot:discoverable",
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value",
+ "misskey": "https://misskey-hub.net/ns#",
+ "_misskey_content": "misskey:_misskey_content",
+ "_misskey_quote": "misskey:_misskey_quote",
+ "_misskey_reaction": "misskey:_misskey_reaction",
+ "_misskey_votes": "misskey:_misskey_votes",
+ "_misskey_talk": "misskey:_misskey_talk",
+ "isCat": "misskey:isCat",
+ "vcard": "http://www.w3.org/2006/vcard/ns#"
+ }
+ ],
+ "id": "https://misskey.io/notes/934gok3482",
+ "type": "Note",
+ "attributedTo": "https://misskey.io/users/93492q0ip0",
+ "summary": null,
+ "content": "i quompt u
RE: https://example.com/objects/30c543fb-a165-40dd-87fd-4e249ec5a40b
",
+ "_misskey_content": "i quompt u",
+ "source": {
+ "content": "i quompt u",
+ "mediaType": "text/x.misskeymarkdown"
+ },
+ "_misskey_quote": "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924",
+ "quoteUrl": "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924",
+ "published": "2022-07-25T15:21:48.208Z",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "cc": [
+ "https://misskey.io/users/93492q0ip0/followers"
+ ],
+ "inReplyTo": null,
+ "attachment": [],
+ "sensitive": false,
+ "tag": []
+}
diff --git a/test/fixtures/quote_post/fedibird_quote_post.json b/test/fixtures/quote_post/fedibird_quote_post.json
new file mode 100644
index 000000000..ebf383356
--- /dev/null
+++ b/test/fixtures/quote_post/fedibird_quote_post.json
@@ -0,0 +1,52 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "ostatus": "http://ostatus.org#",
+ "atomUri": "ostatus:atomUri",
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "sensitive": "as:sensitive",
+ "toot": "http://joinmastodon.org/ns#",
+ "votersCount": "toot:votersCount",
+ "expiry": "toot:expiry"
+ }
+ ],
+ "id": "https://fedibird.com/users/noellabo/statuses/107663670404015196",
+ "type": "Note",
+ "summary": null,
+ "inReplyTo": null,
+ "published": "2022-01-22T02:07:16Z",
+ "url": "https://fedibird.com/@noellabo/107663670404015196",
+ "attributedTo": "https://fedibird.com/users/noellabo",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "cc": [
+ "https://fedibird.com/users/noellabo/followers"
+ ],
+ "sensitive": false,
+ "atomUri": "https://fedibird.com/users/noellabo/statuses/107663670404015196",
+ "inReplyToAtomUri": null,
+ "conversation": "tag:fedibird.com,2022-01-22:objectId=107663670404038002:objectType=Conversation",
+ "context": "https://fedibird.com/contexts/107663670404038002",
+ "quoteURL": "https://misskey.io/notes/8vsn2izjwh",
+ "_misskey_quote": "https://misskey.io/notes/8vsn2izjwh",
+ "_misskey_content": "ãã¤ã®çã¾ãã ã·ããªã³",
+ "content": "ãã¤ã®çã¾ãã ã·ããªã³
QT: https://misskey.io/notes/8vsn2izjwh
",
+ "contentMap": {
+ "ja": "ãã¤ã®çã¾ãã ã·ããªã³
QT: https://misskey.io/notes/8vsn2izjwh
"
+ },
+ "attachment": [],
+ "tag": [],
+ "replies": {
+ "id": "https://fedibird.com/users/noellabo/statuses/107663670404015196/replies",
+ "type": "Collection",
+ "first": {
+ "type": "CollectionPage",
+ "next": "https://fedibird.com/users/noellabo/statuses/107663670404015196/replies?only_other_accounts=true&page=true",
+ "partOf": "https://fedibird.com/users/noellabo/statuses/107663670404015196/replies",
+ "items": []
+ }
+ }
+}
diff --git a/test/fixtures/quote_post/fedibird_quote_uri.json b/test/fixtures/quote_post/fedibird_quote_uri.json
new file mode 100644
index 000000000..7c328fdb9
--- /dev/null
+++ b/test/fixtures/quote_post/fedibird_quote_uri.json
@@ -0,0 +1,54 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "ostatus": "http://ostatus.org#",
+ "atomUri": "ostatus:atomUri",
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "sensitive": "as:sensitive",
+ "toot": "http://joinmastodon.org/ns#",
+ "votersCount": "toot:votersCount",
+ "fedibird": "http://fedibird.com/ns#",
+ "quoteUri": "fedibird:quoteUri",
+ "expiry": "fedibird:expiry"
+ }
+ ],
+ "id": "https://fedibird.com/users/noellabo/statuses/107699335988346142",
+ "type": "Note",
+ "summary": null,
+ "inReplyTo": null,
+ "published": "2022-01-28T09:17:30Z",
+ "url": "https://fedibird.com/@noellabo/107699335988346142",
+ "attributedTo": "https://fedibird.com/users/noellabo",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "cc": [
+ "https://fedibird.com/users/noellabo/followers"
+ ],
+ "sensitive": false,
+ "atomUri": "https://fedibird.com/users/noellabo/statuses/107699335988346142",
+ "inReplyToAtomUri": null,
+ "conversation": "tag:fedibird.com,2022-01-28:objectId=107699335988345290:objectType=Conversation",
+ "context": "https://fedibird.com/contexts/107699335988345290",
+ "quoteUri": "https://fedibird.com/users/yamako/statuses/107699333438289729",
+ "_misskey_quote": "https://fedibird.com/users/yamako/statuses/107699333438289729",
+ "_misskey_content": "ç¾å³ããã",
+ "content": "ç¾å³ããã
QT: https://fedibird.com/@yamako/107699333438289729
",
+ "contentMap": {
+ "ja": "ç¾å³ããã
QT: https://fedibird.com/@yamako/107699333438289729
"
+ },
+ "attachment": [],
+ "tag": [],
+ "replies": {
+ "id": "https://fedibird.com/users/noellabo/statuses/107699335988346142/replies",
+ "type": "Collection",
+ "first": {
+ "type": "CollectionPage",
+ "next": "https://fedibird.com/users/noellabo/statuses/107699335988346142/replies?only_other_accounts=true&page=true",
+ "partOf": "https://fedibird.com/users/noellabo/statuses/107699335988346142/replies",
+ "items": []
+ }
+ }
+}
diff --git a/test/fixtures/quote_post/misskey_quote_post.json b/test/fixtures/quote_post/misskey_quote_post.json
new file mode 100644
index 000000000..59f677ca9
--- /dev/null
+++ b/test/fixtures/quote_post/misskey_quote_post.json
@@ -0,0 +1,46 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "sensitive": "as:sensitive",
+ "Hashtag": "as:Hashtag",
+ "quoteUrl": "as:quoteUrl",
+ "toot": "http://joinmastodon.org/ns#",
+ "Emoji": "toot:Emoji",
+ "featured": "toot:featured",
+ "discoverable": "toot:discoverable",
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value",
+ "misskey": "https://misskey.io/ns#",
+ "_misskey_content": "misskey:_misskey_content",
+ "_misskey_quote": "misskey:_misskey_quote",
+ "_misskey_reaction": "misskey:_misskey_reaction",
+ "_misskey_votes": "misskey:_misskey_votes",
+ "_misskey_talk": "misskey:_misskey_talk",
+ "isCat": "misskey:isCat",
+ "vcard": "http://www.w3.org/2006/vcard/ns#"
+ }
+ ],
+ "id": "https://misskey.io/notes/8vs6ylpfez",
+ "type": "Note",
+ "attributedTo": "https://misskey.io/users/7rkrarq81i",
+ "summary": null,
+ "content": "æ稿è
ã®è¨å®ã«ããã
Fanboxã«ã¤ãã¦ãæ稿è
ã«ãã£ã¦ã¯éå»ã®æ稿ã¯é«é¡ãªãã©ã³ã«ç§»åãã¦ããã¨ããã
RE: https://misskey.io/notes/8vs6wxufd0
",
+ "_misskey_content": "æ稿è
ã®è¨å®ã«ããã\nFanboxã«ã¤ãã¦ãæ稿è
ã«ãã£ã¦ã¯éå»ã®æ稿ã¯é«é¡ãªãã©ã³ã«ç§»åãã¦ããã¨ããã",
+ "_misskey_quote": "https://misskey.io/notes/8vs6wxufd0",
+ "quoteUrl": "https://misskey.io/notes/8vs6wxufd0",
+ "published": "2022-01-21T16:38:30.243Z",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "cc": [
+ "https://misskey.io/users/7rkrarq81i/followers"
+ ],
+ "inReplyTo": null,
+ "attachment": [],
+ "sensitive": false,
+ "tag": []
+}
diff --git a/test/fixtures/quoted_status.json b/test/fixtures/quoted_status.json
new file mode 100644
index 000000000..5030d1a2d
--- /dev/null
+++ b/test/fixtures/quoted_status.json
@@ -0,0 +1,38 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://example.com/schemas/litepub-0.1.jsonld",
+ {
+ "@language": "und"
+ }
+ ],
+ "actor": "https://example.com/users/user",
+ "attachment": [
+ {
+ "mediaType": "image/png",
+ "name": "",
+ "type": "Document",
+ "url": "https://example.com/media/4d6097ae20200ac371f51d24eae0a94cb4b424b6aff81dcc0f7411b1a74c796f.png"
+ }
+ ],
+ "attributedTo": "https://example.com/users/user",
+ "cc": [
+ "https://example.com/users/user/followers"
+ ],
+ "content": "",
+ "context": "https://example.com/contexts/c2c52511-977e-4168-996c-bcf006789dca",
+ "conversation": "https://example.com/contexts/c2c52511-977e-4168-996c-bcf006789dca",
+ "id": "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924",
+ "published": "2022-07-24T17:25:51.614495Z",
+ "sensitive": null,
+ "source": {
+ "content": "",
+ "mediaType": "text/plain"
+ },
+ "summary": "",
+ "tag": [],
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type": "Note"
+}
diff --git a/test/pleroma/web/activity_pub/builder_test.exs b/test/pleroma/web/activity_pub/builder_test.exs
index 3fe32bce5..640caa2b6 100644
--- a/test/pleroma/web/activity_pub/builder_test.exs
+++ b/test/pleroma/web/activity_pub/builder_test.exs
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.BuilderTest do
test "returns note data" do
user = insert(:user)
note = insert(:note)
+ quote = insert(:note)
user2 = insert(:user)
user3 = insert(:user)
@@ -25,7 +26,8 @@ defmodule Pleroma.Web.ActivityPub.BuilderTest do
tags: [name: "jimm"],
summary: "test summary",
cc: [user3.ap_id],
- extra: %{"custom_tag" => "test"}
+ extra: %{"custom_tag" => "test"},
+ quote: quote
}
expected = %{
@@ -39,7 +41,8 @@ defmodule Pleroma.Web.ActivityPub.BuilderTest do
"tag" => ["jimm"],
"to" => [user2.ap_id],
"type" => "Note",
- "custom_tag" => "test"
+ "custom_tag" => "test",
+ "quoteUri" => quote.data["id"]
}
assert {:ok, ^expected, []} = Builder.note(draft)
diff --git a/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs
new file mode 100644
index 000000000..4e0910d3e
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs
@@ -0,0 +1,56 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do
+ alias Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy
+ use Pleroma.DataCase
+
+ test "adds quote URL to post content" do
+ quote_url = "https://example.com/objects/1234"
+
+ activity = %{
+ "type" => "Create",
+ "actor" => "https://example.com/users/alex",
+ "object" => %{
+ "type" => "Note",
+ "content" => "Nice post
",
+ "quoteUri" => quote_url
+ }
+ }
+
+ {:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity)
+
+ assert filtered ==
+ "Nice post
RE: https://example.com/objects/1234
"
+ end
+
+ test "ignores Misskey quote posts" do
+ object = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!()
+
+ activity = %{
+ "type" => "Create",
+ "actor" => "https://misskey.io/users/7rkrarq81i",
+ "object" => object
+ }
+
+ {:ok, filtered} = InlineQuotePolicy.filter(activity)
+ assert filtered == activity
+ end
+
+ test "ignores Fedibird quote posts" do
+ object = File.read!("test/fixtures/quote_post/fedibird_quote_post.json") |> Jason.decode!()
+
+ # Normally the ObjectValidator will fix this before it reaches MRF
+ object = Map.put(object, "quoteUrl", object["quoteURL"])
+
+ activity = %{
+ "type" => "Create",
+ "actor" => "https://fedibird.com/users/noellabo",
+ "object" => object
+ }
+
+ {:ok, filtered} = InlineQuotePolicy.filter(activity)
+ assert filtered == activity
+ end
+end
diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs
index 8b3982916..80290a6e3 100644
--- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs
+++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs
@@ -143,5 +143,61 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest
}
} = ArticleNotePageValidator.cast_and_validate(note)
end
+
+ 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!()
+
+ %{
+ valid?: true,
+ changes: %{
+ quoteUri: "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924"
+ }
+ } = ArticleNotePageValidator.cast_and_validate(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!()
+
+ %{
+ valid?: true,
+ changes: %{
+ quoteUri: "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924"
+ }
+ } = ArticleNotePageValidator.cast_and_validate(note)
+ end
end
end
diff --git a/test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs
index 98ab9e717..90aa9398f 100644
--- a/test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs
@@ -193,10 +193,14 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do
assert response["irreversible"] == true
- assert response["expires_at"] ==
- NaiveDateTime.utc_now()
- |> NaiveDateTime.add(in_seconds)
- |> Pleroma.Web.CommonAPI.Utils.to_masto_date()
+ expected_time =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(in_seconds)
+
+ assert NaiveDateTime.diff(
+ NaiveDateTime.from_iso8601!(response["expires_at"]),
+ expected_time
+ ) < 5
filter = Filter.get(response["id"], user)
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index c9f3f66be..b0efddb2a 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -1944,4 +1944,102 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
} = result
end
end
+
+ describe "posting quotes" do
+ setup do: oauth_access(["write:statuses"])
+
+ test "posting a quote", %{conn: conn} do
+ user = insert(:user)
+ {:ok, quoted_status} = CommonAPI.post(user, %{status: "tell me, for whom do you fight?"})
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "Hmph, how very glib",
+ "quote_id" => quoted_status.id
+ })
+
+ response = json_response_and_validate_schema(conn, 200)
+
+ assert response["quote_id"] == quoted_status.id
+ assert response["quote"]["id"] == quoted_status.id
+ assert response["quote"]["content"] == quoted_status.object.data["content"]
+ end
+
+ test "posting a quote, quoting a status that isn't public", %{conn: conn} do
+ user = insert(:user)
+
+ Enum.each(["private", "local", "direct"], fn visibility ->
+ {:ok, quoted_status} =
+ CommonAPI.post(user, %{
+ status: "tell me, for whom do you fight?",
+ visibility: visibility
+ })
+
+ assert %{"error" => "You can only quote public or unlisted statuses"} =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "Hmph, how very glib",
+ "quote_id" => quoted_status.id
+ })
+ |> json_response_and_validate_schema(422)
+ end)
+ end
+
+ test "posting a quote, after quote, the status gets deleted", %{conn: conn} do
+ user = insert(:user)
+
+ {:ok, quoted_status} =
+ CommonAPI.post(user, %{status: "tell me, for whom do you fight?", visibility: "public"})
+
+ resp =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "I fight for eorzea!",
+ "quote_id" => quoted_status.id
+ })
+ |> json_response_and_validate_schema(200)
+
+ {:ok, _} = CommonAPI.delete(quoted_status.id, user)
+
+ resp =
+ conn
+ |> get("/api/v1/statuses/#{resp["id"]}")
+ |> json_response_and_validate_schema(200)
+
+ assert is_nil(resp["quote"])
+ end
+
+ test "posting a quote of a deleted status", %{conn: conn} do
+ user = insert(:user)
+
+ {:ok, quoted_status} =
+ CommonAPI.post(user, %{status: "tell me, for whom do you fight?", visibility: "public"})
+
+ {:ok, _} = CommonAPI.delete(quoted_status.id, user)
+
+ assert %{"error" => _} =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "I fight for eorzea!",
+ "quote_id" => quoted_status.id
+ })
+ |> json_response_and_validate_schema(422)
+ end
+
+ test "posting a quote of a status that doesn't exist", %{conn: conn} do
+ assert %{"error" => "You can't quote a status that doesn't exist"} =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "I fight for eorzea!",
+ "quote_id" => "oops"
+ })
+ |> json_response_and_validate_schema(422)
+ end
+ end
end
diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
index caf2594c0..9ef44caca 100644
--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -305,7 +305,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
},
akkoma: %{
source: HTML.filter_tags(object_data["content"])
- }
+ },
+ quote_id: nil,
+ quote: nil
}
assert status == expected
@@ -393,6 +395,30 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
assert status.in_reply_to_id == to_string(note.id)
end
+ test "a quote" do
+ note = insert(:note_activity)
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "hehe", quote_id: note.id})
+
+ status = StatusView.render("show.json", %{activity: activity})
+
+ assert status.quote_id == to_string(note.id)
+
+ [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
+
+ assert status.quote_id == to_string(note.id)
+ end
+
+ test "a quote that we can't resolve" do
+ note = insert(:note_activity, quoteUri: "oopsie")
+
+ status = StatusView.render("show.json", %{activity: note})
+
+ assert is_nil(status.quote_id)
+ assert is_nil(status.quote)
+ end
+
test "contains mentions" do
user = insert(:user)
mentioned = insert(:user)
--
2.45.2