Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
43 KB
Referenced Files
None
Subscribers
None
diff --git a/changelog.d/grouped_notifications_flag.skip b/changelog.d/grouped_notifications_flag.skip
new file mode 100644
index 000000000..e69de29bb
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index d64ce4fb5..1a0f63ffa 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -1,438 +1,441 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.InstanceView do
use Pleroma.Web, :view
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
alias Pleroma.Config
alias Pleroma.Web.ActivityPub.MRF
@mastodon_api_level "2.7.2"
@block_severities %{
federated_timeline_removal: "silence",
reject: "suspend"
}
+ def mastodon_api_level, do: @mastodon_api_level
+
def render("show.json", _) do
instance = Config.get(:instance)
common_information(instance)
|> Map.merge(%{
uri: Pleroma.Web.WebFinger.host(),
description: Keyword.get(instance, :description),
short_description: Keyword.get(instance, :short_description),
email: Keyword.get(instance, :email),
urls: %{
streaming_api: Pleroma.Web.Endpoint.websocket_url()
},
stats: Pleroma.Stats.get_stats(),
thumbnail:
URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail))
|> to_string,
registrations: Keyword.get(instance, :registrations_open),
approval_required: Keyword.get(instance, :account_approval_required),
contact_account: contact_account(Keyword.get(instance, :contact_username)),
configuration: configuration(),
# Extra (not present in Mastodon):
max_toot_chars: Keyword.get(instance, :limit),
max_media_attachments: Keyword.get(instance, :max_media_attachments),
poll_limits: Keyword.get(instance, :poll_limits),
upload_limit: Keyword.get(instance, :upload_limit),
avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
background_upload_limit: Keyword.get(instance, :background_upload_limit),
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image),
shout_limit: Config.get([:shout, :limit]),
description_limit: Keyword.get(instance, :description_limit),
chat_limit: Keyword.get(instance, :chat_limit),
pleroma: pleroma_configuration(instance)
})
end
def render("show2.json", _) do
instance = Config.get(:instance)
common_information(instance)
|> Map.merge(%{
domain: Pleroma.Web.WebFinger.host(),
source_url: Pleroma.Application.repository(),
description: Keyword.get(instance, :short_description),
usage: %{users: %{active_month: Pleroma.User.active_user_count()}},
thumbnail: %{
url:
URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail))
|> to_string
},
configuration: configuration2(),
registrations: %{
enabled: Keyword.get(instance, :registrations_open),
approval_required: Keyword.get(instance, :account_approval_required),
message: nil,
url: nil
},
contact: %{
email: Keyword.get(instance, :email),
account: contact_account(Keyword.get(instance, :contact_username))
},
# Extra (not present in Mastodon):
pleroma: pleroma_configuration2(instance)
})
end
def render("rules.json", _) do
Pleroma.Rule.query()
|> Pleroma.Repo.all()
|> render_many(__MODULE__, "rule.json", as: :rule)
end
def render("rule.json", %{rule: rule}) do
%{
id: to_string(rule.id),
text: rule.text,
hint: rule.hint || ""
}
end
def render("domain_blocks.json", _) do
if Config.get([:mrf, :transparency]) do
exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
domain_blocks =
Config.get(:mrf_simple)
|> Enum.map(fn {rule, instances} ->
instances
|> Enum.map(fn
{host, reason} when not_empty_string(host) and not_empty_string(reason) ->
{host, reason}
{host, _reason} when not_empty_string(host) ->
{host, ""}
host when not_empty_string(host) ->
{host, ""}
_ ->
nil
end)
|> Enum.reject(&is_nil/1)
|> Enum.reject(fn {host, _} ->
host in exclusions or not Map.has_key?(@block_severities, rule)
end)
|> Enum.map(fn {host, reason} ->
domain_block = %{
domain: host,
digest: :crypto.hash(:sha256, host) |> Base.encode16(case: :lower),
severity: Map.get(@block_severities, rule)
}
if not_empty_string(reason) do
Map.put(domain_block, :comment, reason)
else
domain_block
end
end)
end)
|> List.flatten()
domain_blocks
else
[]
end
end
def render("translation_languages.json", _) do
with true <- Pleroma.Language.Translation.configured?(),
{:ok, languages} <- Pleroma.Language.Translation.languages_matrix() do
languages
else
_ -> %{}
end
end
defp common_information(instance) do
%{
languages: Keyword.get(instance, :languages, ["en"]),
rules: render(__MODULE__, "rules.json"),
title: Keyword.get(instance, :name),
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})"
}
end
def features do
[
"pleroma_api",
"mastodon_api",
+ "mastodon_api_grouped_notifications",
"mastodon_api_streaming",
"polls",
"v2_suggestions",
"pleroma_explicit_addressing",
"shareable_emoji_packs",
"multifetch",
"pleroma:api/v1/notifications:include_types_filter",
"editing",
"quote_posting",
if Config.get([:activitypub, :blockers_visible]) do
"blockers_visible"
end,
if Config.get([:media_proxy, :enabled]) do
"media_proxy"
end,
if Config.get([:gopher, :enabled]) do
"gopher"
end,
# backwards compat
if Config.get([:shout, :enabled]) do
"chat"
end,
if Config.get([:shout, :enabled]) do
"shout"
end,
if Config.get([:instance, :allow_relay]) do
"relay"
end,
if Config.get([:instance, :safe_dm_mentions]) do
"safe_dm_mentions"
end,
"pleroma_emoji_reactions",
"pleroma_custom_emoji_reactions",
"pleroma_chat_messages",
"pleroma:pin_chats",
if Config.get([:instance, :show_reactions]) do
"exposable_reactions"
end,
if Config.get([:instance, :profile_directory]) do
"profile_directory"
end,
"pleroma:get:main/ostatus",
"pleroma:group_actors",
"pleroma:bookmark_folders",
if Pleroma.Language.LanguageDetector.configured?() do
"pleroma:language_detection"
end,
"pleroma:block_expiration"
]
|> Enum.filter(& &1)
end
def federation do
quarantined = Config.get([:instance, :quarantined_instances], [])
rejected = Config.get([:instance, :rejected_instances], [])
if Config.get([:mrf, :transparency]) do
{:ok, data} = MRF.describe()
data
|> Map.put(
:quarantined_instances,
Enum.map(quarantined, fn {instance, _reason} -> instance end)
)
# This is for backwards compatibility. We originally didn't sent
# extra info like a reason why an instance was rejected/quarantined/etc.
# Because we didn't want to break backwards compatibility it was decided
# to add an extra "info" key.
|> Map.put(:quarantined_instances_info, %{
"quarantined_instances" =>
quarantined
|> Enum.map(fn {instance, reason} -> {instance, %{"reason" => reason}} end)
|> Map.new()
})
|> Map.put(
:rejected_instances,
rejected
|> Enum.map(fn {instance, reason} -> {instance, %{"reason" => reason}} end)
|> Map.new()
)
else
%{}
end
|> Map.put(:enabled, Config.get([:instance, :federating]))
end
defp fields_limits do
%{
max_fields: Config.get([:instance, :max_account_fields]),
max_remote_fields: Config.get([:instance, :max_remote_account_fields]),
name_length: Config.get([:instance, :account_field_name_length]),
value_length: Config.get([:instance, :account_field_value_length])
}
end
defp contact_account(nil), do: nil
defp contact_account("@" <> username) do
contact_account(username)
end
defp contact_account(username) do
user = Pleroma.User.get_cached_by_nickname(username)
if user do
Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user, for: nil})
else
nil
end
end
defp configuration do
%{
accounts: %{
max_featured_tags: 0
},
statuses: %{
max_characters: Config.get([:instance, :limit]),
max_media_attachments: Config.get([:instance, :max_media_attachments])
},
media_attachments: %{
image_size_limit: Config.get([:instance, :upload_limit]),
video_size_limit: Config.get([:instance, :upload_limit]),
supported_mime_types: ["application/octet-stream"]
},
polls: %{
max_options: Config.get([:instance, :poll_limits, :max_options]),
max_characters_per_option: Config.get([:instance, :poll_limits, :max_option_chars]),
min_expiration: Config.get([:instance, :poll_limits, :min_expiration]),
max_expiration: Config.get([:instance, :poll_limits, :max_expiration])
}
}
end
defp configuration2 do
configuration()
|> put_in([:accounts, :max_pinned_statuses], Config.get([:instance, :max_pinned_statuses], 0))
|> put_in([:accounts, :max_profile_fields], Config.get([:instance, :max_account_fields]))
|> put_in(
[:accounts, :profile_field_name_limit],
Config.get([:instance, :account_field_name_length])
)
|> put_in(
[:accounts, :profile_field_value_limit],
Config.get([:instance, :account_field_value_length])
)
|> put_in([:statuses, :characters_reserved_per_url], 0)
|> Map.merge(%{
urls: %{
streaming: Pleroma.Web.Endpoint.websocket_url(),
status: Config.get([:instance, :status_page])
},
vapid: %{
public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
},
translation: %{enabled: Pleroma.Language.Translation.configured?()},
timelines_access: %{
live_feeds: timelines_access(),
hashtag_feeds: timelines_access(),
# not implemented in Pleroma
trending_link_feeds: %{
local: "disabled",
remote: "disabled"
}
}
})
end
defp timelines_access do
%{
local: timeline_access(:local),
remote: timeline_access(:federated)
}
end
defp timeline_access(kind) do
if Config.restrict_unauthenticated_access?(:timelines, kind) do
"authenticated"
else
"public"
end
end
defp pleroma_configuration(instance) do
base_urls = %{}
base_urls =
if Config.get([:media_proxy, :enabled]) do
Map.put(base_urls, :media_proxy, Config.get([:media_proxy, :base_url]))
else
base_urls
end
base_urls =
case Config.get([Pleroma.Upload, :base_url]) do
nil -> base_urls
url -> Map.put(base_urls, :upload, url)
end
%{
metadata: %{
account_activation_required: Keyword.get(instance, :account_activation_required),
features: features(),
federation: federation(),
fields_limits: fields_limits(),
post_formats: Config.get([:instance, :allowed_post_formats]),
birthday_required: Config.get([:instance, :birthday_required]),
birthday_min_age: Config.get([:instance, :birthday_min_age]),
translation: supported_languages(),
base_urls: base_urls,
markup: markup()
},
stats: %{mau: Pleroma.User.active_user_count()},
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
}
end
defp pleroma_configuration2(instance) do
configuration = pleroma_configuration(instance)
configuration
|> Map.merge(%{
metadata:
configuration.metadata
|> Map.merge(%{
avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
background_upload_limit: Keyword.get(instance, :background_upload_limit),
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
background_image:
Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image),
chat_limit: Keyword.get(instance, :chat_limit),
description_limit: Keyword.get(instance, :description_limit),
shout_limit: Config.get([:shout, :limit])
})
})
end
defp supported_languages do
enabled = Pleroma.Language.Translation.configured?()
source_languages =
with true <- enabled,
{:ok, languages} <- Pleroma.Language.Translation.supported_languages(:source) do
languages
else
_ -> nil
end
target_languages =
with true <- enabled,
{:ok, languages} <- Pleroma.Language.Translation.supported_languages(:target) do
languages
else
_ -> nil
end
%{
source_languages: source_languages,
target_languages: target_languages
}
end
defp markup do
%{
allow_inline_images: Config.get([:markup, :allow_inline_images]),
allow_headings: Config.get([:markup, :allow_headings]),
allow_tables: Config.get([:markup, :allow_tables])
}
end
end
diff --git a/test/pleroma/web/mastodon_api/views/instance_view_test.exs b/test/pleroma/web/mastodon_api/views/instance_view_test.exs
new file mode 100644
index 000000000..66485be0e
--- /dev/null
+++ b/test/pleroma/web/mastodon_api/views/instance_view_test.exs
@@ -0,0 +1,675 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.InstanceViewTest do
+ use Pleroma.DataCase, async: true
+
+ import Pleroma.Factory
+
+ alias Pleroma.Language.LanguageDetectorMock
+ alias Pleroma.StaticStubbedConfigMock
+ alias Pleroma.Web.MastodonAPI.InstanceView
+
+ import Mox
+
+ @common_information_keys [
+ :languages,
+ :rules,
+ :title,
+ :version
+ ]
+
+ # Used for filtering out values from configuration.json render in configuration2.json render
+ @configuration2_filter [
+ {:accounts,
+ [
+ :max_pinned_statuses,
+ :max_profile_fields,
+ :profile_field_name_limit,
+ :profile_field_value_limit
+ ]},
+ {:statuses, [:characters_reserved_per_url]},
+ {:urls, [:streaming, :status]},
+ {:vapid, [:public_key]},
+ {:translation, [:enabled]},
+ {:timelines_access, [:live_feeds, :hashtag_feeds, :trending_link_feeds]}
+ ]
+
+ @pleroma_configuration_filter [{:metadata, [:features, :federation]}]
+
+ @pleroma_configuration2_filter [
+ {:metadata,
+ [
+ :avatar_upload_limit,
+ :background_upload_limit,
+ :banner_upload_limit,
+ :background_image,
+ :chat_limit,
+ :description_limit,
+ :shout_limit
+ ]}
+ ]
+
+ @show_keys @common_information_keys ++
+ [
+ :uri,
+ :description,
+ :short_description,
+ :email,
+ :urls,
+ :stats,
+ :thumbnail,
+ :registrations,
+ :approval_required,
+ :contact_account,
+ :configuration,
+ # Extra (not present in Mastodon)
+ :max_toot_chars,
+ :max_media_attachments,
+ :poll_limits,
+ :upload_limit,
+ :avatar_upload_limit,
+ :background_upload_limit,
+ :banner_upload_limit,
+ :background_image,
+ :shout_limit,
+ :description_limit,
+ :chat_limit,
+ :pleroma
+ ]
+
+ # Checked in other check_* functions
+ @show_filter @common_information_keys ++ [:contact_account, :configuration, :pleroma]
+
+ @show2_keys @common_information_keys ++
+ [
+ :domain,
+ :source_url,
+ :description,
+ :usage,
+ :thumbnail,
+ :configuration,
+ :registrations,
+ :contact,
+ # Extra (not present in Mastodon)
+ :pleroma
+ ]
+
+ @show2_filter @common_information_keys ++
+ [:thumbnail, :configuration, :registrations, :contact, :pleroma]
+
+ @features [
+ "pleroma_api",
+ "mastodon_api",
+ "mastodon_api_grouped_notifications",
+ "mastodon_api_streaming",
+ "polls",
+ "v2_suggestions",
+ "pleroma_explicit_addressing",
+ "shareable_emoji_packs",
+ "multifetch",
+ "pleroma:api/v1/notifications:include_types_filter",
+ "editing",
+ "quote_posting",
+ "pleroma_emoji_reactions",
+ "pleroma_custom_emoji_reactions",
+ "pleroma_chat_messages",
+ "pleroma:pin_chats",
+ "pleroma:get:main/ostatus",
+ "pleroma:group_actors",
+ "pleroma:bookmark_folders",
+ "pleroma:block_expiration"
+ ]
+
+ @configurable_features [
+ "blockers_visible",
+ "media_proxy",
+ "gopher",
+ "chat",
+ "shout",
+ "relay",
+ "safe_dm_mentions",
+ "exposable_reactions",
+ "profile_directory",
+ "pleroma:language_detection"
+ ]
+ @configurable_features_flags [
+ [:activitypub, :blockers_visible],
+ [:media_proxy, :enabled],
+ [:gopher, :enabled],
+ [:shout, :enabled],
+ [:instance, :allow_relay],
+ [:instance, :safe_dm_mentions],
+ [:instance, :show_reactions],
+ [:instance, :profile_directory]
+ ]
+
+ # Filters out values from one layer deep nested Maps.
+ # Filtering types:
+ # - :inclusive -> filters out everything that is not in the filters
+ # - :discriminative -> filters out what is in the filters
+ defp filter_render(input, filter, type, acc)
+
+ defp filter_render(_input_rest, [], type, acc) when type == :inclusive, do: acc
+
+ defp filter_render(input_rest, [], type, acc) when type == :discriminative,
+ do: Map.merge(input_rest, acc)
+
+ defp filter_render(input, filter, type, acc) do
+ {{filter_key, filters}, filter_rest} = List.pop_at(filter, 0)
+ {config_key_values, input_rest} = Map.pop!(input, filter_key)
+
+ filtered =
+ case type do
+ :inclusive ->
+ Map.reject(config_key_values, fn {key, _} -> key not in filters end)
+
+ :discriminative ->
+ Map.reject(config_key_values, fn {key, _} -> key in filters end)
+ end
+
+ filter_render(input_rest, filter_rest, type, Map.put(acc, filter_key, filtered))
+ end
+
+ defp check_common_information(info) do
+ filtered_info = Map.reject(info, fn {key, _value} -> key not in @common_information_keys end)
+
+ expected = %{
+ languages: ["en"],
+ rules: InstanceView.render("rules.json", %{}),
+ title: "Pleroma",
+ version:
+ InstanceView.mastodon_api_level() <>
+ " (compatible; #{Pleroma.Application.named_version()})"
+ }
+
+ Map.equal?(expected, filtered_info)
+ end
+
+ defp check_thumbnail2(info) do
+ thumbnail = info[:thumbnail]
+
+ expected = %{
+ url:
+ URI.merge(Pleroma.Web.Endpoint.url(), "http://localhost:4001/instance/thumbnail.jpeg")
+ |> to_string
+ }
+
+ expected == thumbnail
+ end
+
+ defp check_contact(info, user) do
+ contact = info[:contact]
+
+ expected = %{
+ email: "admin@example.com",
+ account: Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user, for: nil})
+ }
+
+ Map.equal?(expected, contact)
+ end
+
+ defp check_contact_account(info, user) do
+ contact = info[:contact_account]
+ view = Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user, for: nil})
+
+ contact == view
+ end
+
+ defp check_configuration(info) do
+ configuration = info[:configuration]
+
+ expected = %{
+ accounts: %{
+ max_featured_tags: 0
+ },
+ statuses: %{
+ max_characters: 5_000,
+ max_media_attachments: 1_000
+ },
+ media_attachments: %{
+ image_size_limit: 16_000_000,
+ video_size_limit: 16_000_000,
+ supported_mime_types: ["application/octet-stream"]
+ },
+ polls: %{
+ max_options: 20,
+ max_characters_per_option: 200,
+ min_expiration: 0,
+ max_expiration: 365 * 24 * 60 * 60
+ }
+ }
+
+ Map.equal?(expected, configuration)
+ end
+
+ defp check_configuration2(info) do
+ configuration =
+ info[:configuration]
+ |> filter_render(@configuration2_filter, :inclusive, %{})
+
+ expected = %{
+ accounts: %{
+ max_pinned_statuses: 1,
+ max_profile_fields: 10,
+ profile_field_name_limit: 512,
+ profile_field_value_limit: 2048
+ },
+ statuses: %{
+ characters_reserved_per_url: 0
+ },
+ urls: %{
+ streaming: Pleroma.Web.Endpoint.websocket_url(),
+ status: nil
+ },
+ vapid: %{
+ public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
+ },
+ translation: %{enabled: true},
+ timelines_access: %{
+ live_feeds: %{local: "public", remote: "public"},
+ hashtag_feeds: %{local: "public", remote: "public"},
+ # not implemented in Pleroma
+ trending_link_feeds: %{
+ local: "disabled",
+ remote: "disabled"
+ }
+ }
+ }
+
+ Map.equal?(expected, configuration)
+ end
+
+ defp check_registrations2(info) do
+ registrations = info[:registrations]
+
+ expected = %{
+ enabled: true,
+ approval_required: false,
+ message: nil,
+ url: nil
+ }
+
+ Map.equal?(expected, registrations)
+ end
+
+ defp check_pleroma_configuration(info) do
+ configuration =
+ info[:pleroma]
+ |> filter_render(@pleroma_configuration_filter, :discriminative, %{})
+
+ expected = %{
+ metadata: %{
+ account_activation_required: false,
+ fields_limits: %{
+ max_fields: 10,
+ max_remote_fields: 20,
+ name_length: 512,
+ value_length: 2048
+ },
+ post_formats: [
+ "text/plain",
+ "text/html",
+ "text/markdown",
+ "text/bbcode",
+ "text/x.misskeymarkdown"
+ ],
+ birthday_required: false,
+ birthday_min_age: 0,
+ translation: %{
+ source_languages: ["en", "pl"],
+ target_languages: ["en", "pl"]
+ },
+ base_urls: %{},
+ markup: %{
+ allow_inline_images: true,
+ allow_headings: false,
+ allow_tables: false
+ }
+ },
+ stats: %{mau: Pleroma.User.active_user_count()},
+ vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
+ }
+
+ Map.equal?(expected, configuration)
+ end
+
+ defp check_pleroma_configuration2(info) do
+ configuration =
+ info[:pleroma]
+ |> filter_render(@pleroma_configuration2_filter, :inclusive, %{})
+
+ expected = %{
+ metadata: %{
+ avatar_upload_limit: 2_000_000,
+ background_upload_limit: 4_000_000,
+ banner_upload_limit: 4_000_000,
+ background_image: Pleroma.Web.Endpoint.url() <> "/images/city.jpg",
+ chat_limit: 5_000,
+ description_limit: 5_000,
+ shout_limit: 5_000
+ }
+ }
+
+ Map.equal?(expected, configuration)
+ end
+
+ defp check_show(info) do
+ filter = @show_filter
+ filtered_info = Map.reject(info, fn {key, _value} -> key in filter end)
+
+ expected = %{
+ uri: Pleroma.Web.WebFinger.host(),
+ description: "Pleroma: An efficient and flexible fediverse server",
+ short_description: "",
+ email: "admin@example.com",
+ urls: %{
+ streaming_api: Pleroma.Web.Endpoint.websocket_url()
+ },
+ stats: Pleroma.Stats.get_stats(),
+ thumbnail: "http://localhost:4001/instance/thumbnail.jpeg",
+ registrations: true,
+ approval_required: false,
+ max_toot_chars: 5_000,
+ max_media_attachments: 1_000,
+ poll_limits: %{
+ max_options: 20,
+ max_option_chars: 200,
+ min_expiration: 0,
+ max_expiration: 365 * 24 * 60 * 60
+ },
+ upload_limit: 16_000_000,
+ avatar_upload_limit: 2_000_000,
+ background_upload_limit: 4_000_000,
+ banner_upload_limit: 4_000_000,
+ background_image: Pleroma.Web.Endpoint.url() <> "/images/city.jpg",
+ shout_limit: 5_000,
+ description_limit: 5_000,
+ chat_limit: 5_000
+ }
+
+ Map.equal?(expected, filtered_info)
+ end
+
+ defp check_show2(info) do
+ filter = @show2_filter
+ filtered_info = Map.reject(info, fn {key, _value} -> key in filter end)
+
+ expected = %{
+ domain: Pleroma.Web.WebFinger.host(),
+ source_url: Pleroma.Application.repository(),
+ description: "",
+ usage: %{users: %{active_month: Pleroma.User.active_user_count()}}
+ }
+
+ Map.equal?(expected, filtered_info)
+ end
+
+ # When this fails, a new feature flag was probably added. Add it to the macros above.
+ describe "uncofigurable features" do
+ setup do: Enum.each(@configurable_features_flags, &clear_config(&1, false))
+
+ test "returns always enabled features" do
+ features = @features
+
+ assert Enum.sort(InstanceView.features()) == Enum.sort(features)
+ end
+ end
+
+ describe "all features" do
+ setup do
+ Enum.each(@configurable_features_flags, &clear_config(&1, true))
+
+ # Stub the StaticStubbedConfigMock to return our mock for the provider
+ StaticStubbedConfigMock
+ |> stub(:get, fn
+ [Pleroma.Language.LanguageDetector, :provider] -> LanguageDetectorMock
+ _other -> nil
+ end)
+
+ # Stub the LanguageDetectorMock with default implementations
+ LanguageDetectorMock
+ |> stub(:missing_dependencies, fn -> [] end)
+ |> stub(:configured?, fn -> true end)
+
+ :ok
+ end
+
+ test "returns all features including configurable ones" do
+ features = @features ++ @configurable_features
+
+ assert Enum.sort(InstanceView.features()) == Enum.sort(features)
+ end
+ end
+
+ describe "federation/0" do
+ setup do: clear_config([:mrf, :transparency], true)
+
+ test "quarantined instances" do
+ clear_config([:instance, :quarantined_instances], [{"quarantine.example.com", "crimes"}])
+
+ output = InstanceView.federation()
+ assert %{quarantined_instances: ["quarantine.example.com"]} = output
+
+ assert %{
+ quarantined_instances_info: %{
+ "quarantined_instances" => %{"quarantine.example.com" => %{"reason" => "crimes"}}
+ }
+ } = output
+ end
+
+ test "rejected instances" do
+ clear_config([:instance, :rejected_instances], [
+ {"rejected.example.com", "not enough #cofe posting"}
+ ])
+
+ output = InstanceView.federation()
+
+ assert %{
+ rejected_instances: %{
+ "rejected.example.com" => %{"reason" => "not enough #cofe posting"}
+ }
+ } = output
+ end
+
+ test "federating" do
+ clear_config([:instance, :federating], true)
+
+ output = InstanceView.federation()
+ assert %{enabled: true} = output
+ end
+
+ test "transparency" do
+ clear_config([:instance, :quarantined_instances], [{"quarantine.example.com", "crimes"}])
+ clear_config([:instance, :federating], true)
+ clear_config([:mrf, :transparency], false)
+
+ clear_config([:instance, :rejected_instances], [
+ {"rejected.example.com", "not enough #cofe posting"}
+ ])
+
+ output = InstanceView.federation()
+ assert %{enabled: true} == output
+ end
+
+ test "MRFs" do
+ clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
+ clear_config([:mrf_simple, :reject], [{"rejected.example.com", "not enough #cofe posting"}])
+
+ clear_config([:mrf, :transparency_exclusions], [
+ {"media1.example.com", "the Fediverse doesn't need to know"}
+ ])
+
+ clear_config([:mrf_simple, :media_removal], [
+ {"media1.example.com", "NSFW"},
+ {"media2.example.com", "usual suspects"}
+ ])
+
+ output = InstanceView.federation()
+
+ expected = %{
+ mrf_simple: %{
+ reject: ["rejected.example.com"],
+ media_removal: ["media2.example.com"]
+ },
+ mrf_hashtag: %{
+ sensitive: ["nsfw"]
+ },
+ exclusions: true,
+ mrf_policies: ["SimplePolicy", "HashtagPolicy"],
+ mrf_simple_info: %{
+ reject: %{
+ "rejected.example.com" => %{"reason" => "not enough #cofe posting"}
+ },
+ media_removal: %{"media2.example.com" => %{"reason" => "usual suspects"}}
+ }
+ }
+
+ assert expected.mrf_simple.reject == output.mrf_simple.reject
+ assert expected.mrf_simple.media_removal == output.mrf_simple.media_removal
+ assert expected.mrf_simple_info == output.mrf_simple_info
+ assert expected.exclusions == output.exclusions
+ end
+ end
+
+ describe "render show.json/show2.json" do
+ setup do
+ Pleroma.Rule.create(%{text: "rule 1"})
+ Pleroma.Rule.create(%{text: "rule 2"})
+
+ user = insert(:user)
+
+ # contact in show2.json
+ clear_config([:instance, :contact_username], user.nickname)
+
+ # Pleroma.Language.Translation.configured?() -> true
+ clear_config([Pleroma.Language.Translation, :provider], TranslationMock)
+
+ # Deprecated, but still used in view
+ clear_config([:instance, :chat_limit], 5_000)
+
+ %{user: user}
+ end
+
+ test "renders show.json properly", %{user: user} do
+ output = InstanceView.render("show.json", %{})
+ expected_keys = Enum.sort(@show_keys)
+
+ assert check_common_information(output)
+ assert check_show(output)
+ assert check_contact_account(output, user)
+ assert check_configuration(output)
+ assert check_pleroma_configuration(output)
+ assert expected_keys == Enum.sort(Map.keys(output))
+ end
+
+ test "renders show2.json properly", %{user: user} do
+ output = InstanceView.render("show2.json", %{})
+ expected_keys = Enum.sort(@show2_keys)
+
+ assert check_common_information(output)
+ assert check_show2(output)
+ assert check_thumbnail2(output)
+ assert check_configuration2(output)
+ assert check_registrations2(output)
+ assert check_contact(output, user)
+ assert check_pleroma_configuration2(output)
+ assert expected_keys == Enum.sort(Map.keys(output))
+ end
+ end
+
+ describe "render rule.json" do
+ setup do
+ rule = Pleroma.Rule.create(%{text: "hambaga"})
+
+ %{rule: rule}
+ end
+
+ test "renders properly", %{rule: rule} do
+ output = InstanceView.render("rule.json", %{rule: rule})
+
+ assert %{text: "hambaga"} = output
+ end
+ end
+
+ describe "render rules.json" do
+ setup do
+ Pleroma.Rule.create(%{text: "rule 1"})
+ Pleroma.Rule.create(%{text: "rule 2"})
+ Pleroma.Rule.create(%{text: "rule 3"})
+ Pleroma.Rule.create(%{text: "rule 4"})
+
+ :ok
+ end
+
+ test "renders properly" do
+ output = InstanceView.render("rules.json", %{})
+ keys = Enum.map(output, fn %{text: text} -> text end)
+
+ assert length(keys) == 4
+ assert Enum.all?(output, fn %{text: "rule " <> num} -> String.to_integer(num) end)
+ end
+ end
+
+ describe "render domain_blocks.json" do
+ setup do
+ clear_config([:mrf, :transparency], true)
+ clear_config([:mrf_simple, :media_removal], [{"media1.example.com", "NSFW"}])
+
+ clear_config([:mrf, :transparency_exclusions], [
+ {"removed1.example.com", "the Fediverse doesn't need to know"}
+ ])
+
+ clear_config([:mrf_simple, :reject], [
+ {"rejected.example.com", "not enough #cofe posting"},
+ {"rejected-without-reason.example.com", ""}
+ ])
+
+ clear_config([:mrf_simple, :federated_timeline_removal], [
+ {"removed1.example.com", "spam"},
+ {"removed2.example.com", "more spam"}
+ ])
+
+ :ok
+ end
+
+ test "renders properly" do
+ output = InstanceView.render("domain_blocks.json", %{})
+
+ # Media removal is not in @block_severities in the view.
+ # NOTE: Order of these two matters!
+ expected = [
+ %{
+ domain: "removed2.example.com",
+ comment: "more spam",
+ severity: "silence",
+ digest: "5f6649cfc780fac5172dbbcc8a552953f6621a14368d82728b6e8faf51295dc0"
+ },
+ %{
+ domain: "rejected.example.com",
+ comment: "not enough #cofe posting",
+ severity: "suspend",
+ digest: "c15082d347fb7eccd251e199a98dcdce94a8dfc1d33d6ece80889eed374b6f92"
+ },
+ %{
+ domain: "rejected-without-reason.example.com",
+ severity: "suspend",
+ digest: "48297155018c626c8867ca0bba4a2621eccdf51c6f7ac91b7099ed8b2ee9863f"
+ }
+ ]
+
+ assert expected == output
+ end
+ end
+
+ describe "render translation_languages.json" do
+ setup do: clear_config([Pleroma.Language.Translation, :provider], TranslationMock)
+
+ test "returns language matrix" do
+ output = InstanceView.render("translation_languages.json", %{})
+
+ assert %{"en" => ["pl"], "pl" => ["en"]} == output
+ end
+ end
+end
diff --git a/test/pleroma/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs
index afe4ebb36..e2bf3400a 100644
--- a/test/pleroma/web/node_info_test.exs
+++ b/test/pleroma/web/node_info_test.exs
@@ -1,302 +1,303 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.NodeInfoTest do
use Pleroma.Web.ConnCase, async: false
import Pleroma.Factory
setup do: clear_config([:mrf_simple])
setup do: clear_config(:instance)
test "GET /.well-known/nodeinfo", %{conn: conn} do
links =
conn
|> get("/.well-known/nodeinfo")
|> json_response(200)
|> Map.fetch!("links")
Enum.each(links, fn link ->
href = Map.fetch!(link, "href")
conn
|> get(href)
|> json_response(200)
end)
accept_types = [
"application/activity+json",
"application/json",
"application/jrd+json"
]
for type <- accept_types do
conn
|> put_req_header("accept", type)
|> get("/.well-known/nodeinfo")
|> json_response(200)
end
end
test "nodeinfo shows staff accounts", %{conn: conn} do
moderator = insert(:user, local: true, is_moderator: true)
admin = insert(:user, local: true, is_admin: true)
conn =
conn
|> get("/nodeinfo/2.1.json")
assert result = json_response(conn, 200)
assert moderator.ap_id in result["metadata"]["staffAccounts"]
assert admin.ap_id in result["metadata"]["staffAccounts"]
end
test "nodeinfo shows roles and privileges", %{conn: conn} do
clear_config([:instance, :moderator_privileges], [:cofe])
clear_config([:instance, :admin_privileges], [:suya, :cofe])
conn =
conn
|> get("/nodeinfo/2.1.json")
assert result = json_response(conn, 200)
assert %{"admin" => ["suya", "cofe"], "moderator" => ["cofe"]} == result["metadata"]["roles"]
end
test "nodeinfo shows restricted nicknames", %{conn: conn} do
conn =
conn
|> get("/nodeinfo/2.1.json")
assert result = json_response(conn, 200)
assert Config.get([Pleroma.User, :restricted_nicknames]) ==
result["metadata"]["restrictedNicknames"]
end
test "returns software.repository field in nodeinfo 2.1", %{conn: conn} do
conn
|> get("/.well-known/nodeinfo")
|> json_response(200)
conn =
conn
|> get("/nodeinfo/2.1.json")
assert result = json_response(conn, 200)
assert Pleroma.Application.repository() == result["software"]["repository"]
end
test "returns fieldsLimits field", %{conn: conn} do
clear_config([:instance, :max_account_fields], 10)
clear_config([:instance, :max_remote_account_fields], 15)
clear_config([:instance, :account_field_name_length], 255)
clear_config([:instance, :account_field_value_length], 2048)
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["fieldsLimits"]["maxFields"] == 10
assert response["metadata"]["fieldsLimits"]["maxRemoteFields"] == 15
assert response["metadata"]["fieldsLimits"]["nameLength"] == 255
assert response["metadata"]["fieldsLimits"]["valueLength"] == 2048
end
test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do
clear_config([:instance, :safe_dm_mentions], true)
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert "safe_dm_mentions" in response["metadata"]["features"]
clear_config([:instance, :safe_dm_mentions], false)
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
refute "safe_dm_mentions" in response["metadata"]["features"]
end
describe "`metadata/federation/enabled`" do
setup do: clear_config([:instance, :federating])
test "it shows if federation is enabled/disabled", %{conn: conn} do
clear_config([:instance, :federating], true)
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["enabled"] == true
clear_config([:instance, :federating], false)
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["enabled"] == false
end
end
test "it shows default features flags", %{conn: conn} do
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
default_features = [
"pleroma_api",
"mastodon_api",
+ "mastodon_api_grouped_notifications",
"mastodon_api_streaming",
"polls",
"pleroma_explicit_addressing",
"shareable_emoji_packs",
"multifetch",
"pleroma_emoji_reactions",
"pleroma:api/v1/notifications:include_types_filter",
"pleroma_chat_messages"
]
assert MapSet.subset?(
MapSet.new(default_features),
MapSet.new(response["metadata"]["features"])
)
end
describe "Quarantined instances" do
setup do
clear_config([:mrf, :transparency], true)
quarantined_instances = [{"example.com", "reason to quarantine"}]
clear_config([:instance, :quarantined_instances], quarantined_instances)
end
test "shows quarantined instances data if enabled", %{conn: conn} do
expected_config = ["example.com"]
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["quarantined_instances"] == expected_config
end
test "shows extra information in the quarantined_info field for relevant entries", %{
conn: conn
} do
clear_config([:mrf, :transparency], true)
expected_config = %{
"quarantined_instances" => %{
"example.com" => %{"reason" => "reason to quarantine"}
}
}
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["quarantined_instances_info"] == expected_config
end
end
describe "MRF SimplePolicy" do
setup do
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
clear_config([:mrf, :transparency], true)
end
test "shows MRF transparency data if enabled", %{conn: conn} do
simple_config = %{"reject" => [{"example.com", ""}]}
clear_config(:mrf_simple, simple_config)
expected_config = %{"reject" => ["example.com"]}
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["mrf_simple"] == expected_config
end
test "performs exclusions from MRF transparency data if configured", %{conn: conn} do
clear_config([:mrf, :transparency_exclusions], [
{"other.site", "We don't want them to know"}
])
simple_config = %{"reject" => [{"example.com", ""}, {"other.site", ""}]}
clear_config(:mrf_simple, simple_config)
expected_config = %{"reject" => ["example.com"]}
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["mrf_simple"] == expected_config
assert response["metadata"]["federation"]["exclusions"] == true
end
test "shows extra information in the mrf_simple_info field for relevant entries", %{
conn: conn
} do
simple_config = %{
media_removal: [{"no.media", "LEEWWWDD >//<"}],
media_nsfw: [],
federated_timeline_removal: [{"no.ftl", ""}],
report_removal: [],
reject: [
{"example.instance", "Some reason"},
{"uwu.owo", "awoo to much"},
{"no.reason", ""}
],
followers_only: [],
accept: [],
avatar_removal: [],
banner_removal: [],
reject_deletes: [
{"peak.me", "I want to peak at what they don't want me to see, eheh"}
]
}
clear_config(:mrf_simple, simple_config)
clear_config([:mrf, :transparency_exclusions], [
{"peak.me", "I don't want them to know"}
])
expected_config = %{
"media_removal" => %{
"no.media" => %{"reason" => "LEEWWWDD >//<"}
},
"reject" => %{
"example.instance" => %{"reason" => "Some reason"},
"uwu.owo" => %{"reason" => "awoo to much"}
}
}
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["mrf_simple_info"] == expected_config
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Thu, Oct 29, 11:24 AM (21 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1798045
Default Alt Text
(43 KB)

Event Timeline