From 200670b7f44ac4da23baa33022669129a78bf40b Mon Sep 17 00:00:00 2001 From: Georg Krause Date: Sat, 10 Sep 2022 16:49:40 +0000 Subject: [PATCH] Refactor NodeInfo Endpoint to use proper serializer --- api/funkwhale_api/instance/nodeinfo.py | 100 --------- api/funkwhale_api/instance/serializers.py | 200 +++++++++++++++++ api/funkwhale_api/instance/views.py | 81 +++++-- api/tests/instance/test_nodeinfo.py | 253 +++++++--------------- api/tests/instance/test_views.py | 5 +- changes/changelog.d/nodeinfo.enhancement | 1 + 6 files changed, 348 insertions(+), 292 deletions(-) delete mode 100644 api/funkwhale_api/instance/nodeinfo.py create mode 100644 api/funkwhale_api/instance/serializers.py create mode 100644 changes/changelog.d/nodeinfo.enhancement diff --git a/api/funkwhale_api/instance/nodeinfo.py b/api/funkwhale_api/instance/nodeinfo.py deleted file mode 100644 index a40c4e94b..000000000 --- a/api/funkwhale_api/instance/nodeinfo.py +++ /dev/null @@ -1,100 +0,0 @@ -from cache_memoize import cache_memoize - -from django.urls import reverse - -import funkwhale_api -from funkwhale_api.common import preferences -from funkwhale_api.federation import actors, models as federation_models -from funkwhale_api.federation import utils as federation_utils -from funkwhale_api.moderation import models as moderation_models -from funkwhale_api.music import utils as music_utils - -from . import stats - - -def get(): - all_preferences = preferences.all() - share_stats = all_preferences.get("instance__nodeinfo_stats_enabled") - allow_list_enabled = all_preferences.get("moderation__allow_list_enabled") - allow_list_public = all_preferences.get("moderation__allow_list_public") - auth_required = all_preferences.get("common__api_authentication_required") - banner = all_preferences.get("instance__banner") - unauthenticated_report_types = all_preferences.get( - "moderation__unauthenticated_report_types" - ) - if allow_list_enabled and allow_list_public: - allowed_domains = list( - federation_models.Domain.objects.filter(allowed=True) - .order_by("name") - .values_list("name", flat=True) - ) - else: - allowed_domains = None - data = { - "version": "2.0", - "software": {"name": "funkwhale", "version": funkwhale_api.__version__}, - "protocols": ["activitypub"], - "services": {"inbound": [], "outbound": []}, - "openRegistrations": all_preferences.get("users__registration_enabled"), - "usage": {"users": {"total": 0, "activeHalfyear": 0, "activeMonth": 0}}, - "metadata": { - "actorId": actors.get_service_actor().fid, - "private": all_preferences.get("instance__nodeinfo_private"), - "shortDescription": all_preferences.get("instance__short_description"), - "longDescription": all_preferences.get("instance__long_description"), - "rules": all_preferences.get("instance__rules"), - "contactEmail": all_preferences.get("instance__contact_email"), - "terms": all_preferences.get("instance__terms"), - "nodeName": all_preferences.get("instance__name"), - "banner": federation_utils.full_url(banner.url) if banner else None, - "defaultUploadQuota": all_preferences.get("users__upload_quota"), - "library": { - "federationEnabled": all_preferences.get("federation__enabled"), - "anonymousCanListen": not all_preferences.get( - "common__api_authentication_required" - ), - }, - "supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS, - "allowList": {"enabled": allow_list_enabled, "domains": allowed_domains}, - "reportTypes": [ - {"type": t, "label": l, "anonymous": t in unauthenticated_report_types} - for t, l in moderation_models.REPORT_TYPES - ], - "funkwhaleSupportMessageEnabled": all_preferences.get( - "instance__funkwhale_support_message_enabled" - ), - "instanceSupportMessage": all_preferences.get("instance__support_message"), - "endpoints": {"knownNodes": None, "channels": None, "libraries": None}, - }, - } - - if share_stats: - getter = cache_memoize(600, prefix="memoize:instance:stats")(stats.get) - statistics = getter() - data["usage"]["users"]["total"] = statistics["users"]["total"] - data["usage"]["users"]["activeHalfyear"] = statistics["users"][ - "active_halfyear" - ] - data["usage"]["users"]["activeMonth"] = statistics["users"]["active_month"] - data["metadata"]["library"]["tracks"] = {"total": statistics["tracks"]} - data["metadata"]["library"]["artists"] = {"total": statistics["artists"]} - data["metadata"]["library"]["albums"] = {"total": statistics["albums"]} - data["metadata"]["library"]["music"] = {"hours": statistics["music_duration"]} - - data["metadata"]["usage"] = { - "favorites": {"tracks": {"total": statistics["track_favorites"]}}, - "listenings": {"total": statistics["listenings"]}, - "downloads": {"total": statistics["downloads"]}, - } - if not auth_required: - data["metadata"]["endpoints"]["knownNodes"] = federation_utils.full_url( - reverse("api:v1:federation:domains-list") - ) - if not auth_required and preferences.get("federation__public_index"): - data["metadata"]["endpoints"]["libraries"] = federation_utils.full_url( - reverse("federation:index:index-libraries") - ) - data["metadata"]["endpoints"]["channels"] = federation_utils.full_url( - reverse("federation:index:index-channels") - ) - return data diff --git a/api/funkwhale_api/instance/serializers.py b/api/funkwhale_api/instance/serializers.py new file mode 100644 index 000000000..9671c6cd4 --- /dev/null +++ b/api/funkwhale_api/instance/serializers.py @@ -0,0 +1,200 @@ +from rest_framework import serializers + +from funkwhale_api.federation.utils import full_url +from drf_spectacular.utils import extend_schema_field + + +class SoftwareSerializer(serializers.Serializer): + name = serializers.SerializerMethodField() + version = serializers.CharField() + + def get_name(self, obj) -> str: + return "funkwhale" + + +class ServicesSerializer(serializers.Serializer): + inbound = serializers.ListField(child=serializers.CharField(), default=[]) + outbound = serializers.ListField(child=serializers.CharField(), default=[]) + + +class UsersUsageSerializer(serializers.Serializer): + total = serializers.IntegerField() + activeHalfyear = serializers.SerializerMethodField() + activeMonth = serializers.SerializerMethodField() + + def get_activeHalfyear(self, obj) -> int: + return obj.get("active_halfyear", 0) + + def get_activeMonth(self, obj) -> int: + return obj.get("active_month", 0) + + +class UsageSerializer(serializers.Serializer): + users = UsersUsageSerializer() + + +class TotalCountSerializer(serializers.Serializer): + total = serializers.SerializerMethodField() + + def get_total(self, obj) -> int: + return obj + + +class TotalHoursSerializer(serializers.Serializer): + hours = serializers.SerializerMethodField() + + def get_hours(self, obj) -> int: + return obj + + +class NodeInfoLibrarySerializer(serializers.Serializer): + federationEnabled = serializers.BooleanField() + anonymousCanListen = serializers.BooleanField() + tracks = TotalCountSerializer(default=0) + artists = TotalCountSerializer(default=0) + albums = TotalCountSerializer(default=0) + music = TotalHoursSerializer(source="music_duration", default=0) + + +class AllowListStatSerializer(serializers.Serializer): + enabled = serializers.BooleanField() + domains = serializers.ListField(child=serializers.CharField()) + + +class ReportTypeSerializer(serializers.Serializer): + type = serializers.CharField() + label = serializers.CharField() + anonymous = serializers.BooleanField() + + +class EndpointsSerializer(serializers.Serializer): + knownNodes = serializers.URLField(default=None) + channels = serializers.URLField(default=None) + libraries = serializers.URLField(default=None) + + +class MetadataUsageFavoriteSerializer(serializers.Serializer): + tracks = serializers.SerializerMethodField() + + @extend_schema_field(TotalCountSerializer) + def get_tracks(self, obj): + return TotalCountSerializer(obj).data + + +class MetadataUsageSerializer(serializers.Serializer): + favorites = MetadataUsageFavoriteSerializer(source="track_favorites") + listenings = TotalCountSerializer() + downloads = TotalCountSerializer() + + +class MetadataSerializer(serializers.Serializer): + actorId = serializers.CharField() + private = serializers.SerializerMethodField() + shortDescription = serializers.SerializerMethodField() + longDescription = serializers.SerializerMethodField() + rules = serializers.SerializerMethodField() + contactEmail = serializers.SerializerMethodField() + terms = serializers.SerializerMethodField() + nodeName = serializers.SerializerMethodField() + banner = serializers.SerializerMethodField() + defaultUploadQuota = serializers.SerializerMethodField() + library = serializers.SerializerMethodField() + supportedUploadExtensions = serializers.ListField(child=serializers.CharField()) + allowList = serializers.SerializerMethodField() + reportTypes = ReportTypeSerializer(source="report_types", many=True) + funkwhaleSupportMessageEnabled = serializers.SerializerMethodField() + instanceSupportMessage = serializers.SerializerMethodField() + endpoints = EndpointsSerializer() + usage = serializers.SerializerMethodField(source="stats") + + def get_private(self, obj) -> bool: + return obj["preferences"].get("instance__nodeinfo_private") + + def get_shortDescription(self, obj) -> str: + return obj["preferences"].get("instance__short_description") + + def get_longDescription(self, obj) -> str: + return obj["preferences"].get("instance__long_description") + + def get_rules(self, obj) -> str: + return obj["preferences"].get("instance__rules") + + def get_contactEmail(self, obj) -> str: + return obj["preferences"].get("instance__contact_email") + + def get_terms(self, obj) -> str: + return obj["preferences"].get("instance__terms") + + def get_nodeName(self, obj) -> str: + return obj["preferences"].get("instance__name") + + @extend_schema_field(serializers.CharField) + def get_banner(self, obj) -> (str, None): + if obj["preferences"].get("instance__banner"): + return full_url(obj["preferences"].get("instance__banner").url) + return None + + def get_defaultUploadQuota(self, obj) -> int: + return obj["preferences"].get("users__upload_quota") + + def get_library(self, obj) -> bool: + data = obj["stats"] or {} + data["federationEnabled"] = obj["preferences"].get("federation__enabled") + data["anonymousCanListen"] = not obj["preferences"].get( + "common__api_authentication_required" + ) + return NodeInfoLibrarySerializer(data).data + + @extend_schema_field(AllowListStatSerializer) + def get_allowList(self, obj): + return AllowListStatSerializer( + { + "enabled": obj["preferences"].get("moderation__allow_list_enabled"), + "domains": obj["allowed_domains"] or None, + } + ).data + + def get_funkwhaleSupportMessageEnabled(self, obj) -> bool: + return obj["preferences"].get("instance__funkwhale_support_message_enabled") + + def get_instanceSupportMessage(self, obj) -> str: + return obj["preferences"].get("instance__support_message") + + @extend_schema_field(MetadataUsageSerializer) + def get_usage(self, obj): + return MetadataUsageSerializer(obj["stats"]).data + + +class NodeInfo20Serializer(serializers.Serializer): + version = serializers.SerializerMethodField() + software = SoftwareSerializer() + protocols = serializers.SerializerMethodField() + services = ServicesSerializer(default={}) + openRegistrations = serializers.SerializerMethodField() + usage = serializers.SerializerMethodField() + metadata = serializers.SerializerMethodField() + + def get_version(self, obj) -> str: + return "2.0" + + def get_protocols(self, obj) -> list: + return ["activitypub"] + + def get_services(self, obj) -> object: + return {"inbound": [], "outbound": []} + + def get_openRegistrations(self, obj) -> bool: + return obj["preferences"]["users__registration_enabled"] + + @extend_schema_field(UsageSerializer) + def get_usage(self, obj): + usage = None + if obj["preferences"]["instance__nodeinfo_stats_enabled"]: + usage = obj["stats"] + else: + usage = {"users": {"total": 0, "activeMonth": 0, "activeHalfyear": 0}} + return UsageSerializer(usage).data + + @extend_schema_field(MetadataSerializer) + def get_metadata(self, obj): + return MetadataSerializer(obj).data diff --git a/api/funkwhale_api/instance/views.py b/api/funkwhale_api/instance/views.py index 4c2a8aae8..dd8ea7b53 100644 --- a/api/funkwhale_api/instance/views.py +++ b/api/funkwhale_api/instance/views.py @@ -1,21 +1,31 @@ import json import logging +from cache_memoize import cache_memoize from django.conf import settings +from django.urls import reverse -from dynamic_preferences.api import serializers +from dynamic_preferences.api.serializers import GlobalPreferenceSerializer from dynamic_preferences.api import viewsets as preferences_viewsets from dynamic_preferences.registries import global_preferences_registry -from rest_framework import views from rest_framework import generics +from rest_framework import views from rest_framework.response import Response +from funkwhale_api import __version__ as funkwhale_version from funkwhale_api.common import middleware from funkwhale_api.common import preferences from funkwhale_api.federation import utils as federation_utils +from funkwhale_api.federation.models import Domain +from funkwhale_api.federation.actors import get_service_actor from funkwhale_api.users.oauth import permissions as oauth_permissions +from funkwhale_api.music.utils import SUPPORTED_EXTENSIONS +from funkwhale_api.moderation.models import REPORT_TYPES -from . import nodeinfo +from drf_spectacular.utils import extend_schema + +from . import serializers +from . import stats NODEINFO_2_CONTENT_TYPE = "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8" # noqa @@ -32,7 +42,7 @@ class AdminSettings(preferences_viewsets.GlobalPreferencesViewSet): class InstanceSettings(generics.GenericAPIView): permission_classes = [] authentication_classes = [] - serializer_class = serializers.GlobalPreferenceSerializer + serializer_class = GlobalPreferenceSerializer def get_queryset(self): manager = global_preferences_registry.manager() @@ -45,21 +55,66 @@ class InstanceSettings(generics.GenericAPIView): def get(self, request): queryset = self.get_queryset() - serializer = serializers.GlobalPreferenceSerializer(queryset, many=True) - return Response(serializer.data) + data = GlobalPreferenceSerializer(queryset, many=True).data + return Response(data, status=200) class NodeInfo(views.APIView): permission_classes = [] authentication_classes = [] - def get(self, request, *args, **kwargs): - try: - data = nodeinfo.get() - except ValueError: - logger.warn("nodeinfo returned invalid json") - data = {} - return Response(data, status=200, content_type=NODEINFO_2_CONTENT_TYPE) + @extend_schema(responses=serializers.NodeInfo20Serializer) + def get(self, request): + pref = preferences.all() + if ( + pref["moderation__allow_list_public"] + and pref["moderation__allow_list_enabled"] + ): + allowed_domains = list( + Domain.objects.filter(allowed=True) + .order_by("name") + .values_list("name", flat=True) + ) + else: + allowed_domains = None + + data = { + "software": {"version": funkwhale_version}, + "preferences": pref, + "stats": cache_memoize(600, prefix="memoize:instance:stats")(stats.get)() + if pref["instance__nodeinfo_stats_enabled"] + else None, + "actorId": get_service_actor().fid, + "supportedUploadExtensions": SUPPORTED_EXTENSIONS, + "allowed_domains": allowed_domains, + "report_types": [ + { + "type": t, + "label": l, + "anonymous": t + in pref.get("moderation__unauthenticated_report_types"), + } + for t, l in REPORT_TYPES + ], + "endpoints": {}, + } + + if not pref.get("common__api_authentication_required"): + if pref.get("instance__nodeinfo_stats_enabled"): + data["endpoints"]["knownNodes"] = reverse( + "api:v1:federation:domains-list" + ) + if pref.get("federation__public_index"): + data["endpoints"]["libraries"] = reverse( + "federation:index:index-libraries" + ) + data["endpoints"]["channels"] = reverse( + "federation:index:index-channels" + ) + serializer = serializers.NodeInfo20Serializer(data) + return Response( + serializer.data, status=200, content_type=NODEINFO_2_CONTENT_TYPE + ) class SpaManifest(views.APIView): diff --git a/api/tests/instance/test_nodeinfo.py b/api/tests/instance/test_nodeinfo.py index 0f820e691..30b2c9e71 100644 --- a/api/tests/instance/test_nodeinfo.py +++ b/api/tests/instance/test_nodeinfo.py @@ -1,194 +1,97 @@ -import pytest - from django.urls import reverse -import funkwhale_api -from funkwhale_api.instance import nodeinfo -from funkwhale_api.federation import actors -from funkwhale_api.federation import utils as federation_utils -from funkwhale_api.music import utils as music_utils +from collections import OrderedDict -def test_nodeinfo_dump(preferences, mocker, avatar): - preferences["instance__banner"] = avatar - preferences["instance__nodeinfo_stats_enabled"] = True - preferences["common__api_authentication_required"] = False - preferences["moderation__unauthenticated_report_types"] = [ - "takedown_request", - "other", - "other_category_that_doesnt_exist", - ] - - stats = { - "users": {"total": 1, "active_halfyear": 12, "active_month": 13}, - "tracks": 2, - "albums": 3, - "artists": 4, - "track_favorites": 5, - "music_duration": 6, - "listenings": 7, - "downloads": 42, - } - mocker.patch("funkwhale_api.instance.stats.get", return_value=stats) +def test_nodeinfo_default(api_client): + url = reverse("api:v1:instance:nodeinfo-2.0") + response = api_client.get(url) expected = { "version": "2.0", - "software": {"name": "funkwhale", "version": funkwhale_api.__version__}, + "software": OrderedDict([("name", "funkwhale"), ("version", "1.2.7")]), "protocols": ["activitypub"], - "services": {"inbound": [], "outbound": []}, - "openRegistrations": preferences["users__registration_enabled"], - "usage": {"users": {"total": 1, "activeHalfyear": 12, "activeMonth": 13}}, - "metadata": { - "actorId": actors.get_service_actor().fid, - "private": preferences["instance__nodeinfo_private"], - "shortDescription": preferences["instance__short_description"], - "longDescription": preferences["instance__long_description"], - "nodeName": preferences["instance__name"], - "rules": preferences["instance__rules"], - "contactEmail": preferences["instance__contact_email"], - "defaultUploadQuota": preferences["users__upload_quota"], - "terms": preferences["instance__terms"], - "banner": federation_utils.full_url(preferences["instance__banner"].url), - "library": { - "federationEnabled": preferences["federation__enabled"], - "anonymousCanListen": not preferences[ - "common__api_authentication_required" - ], - "tracks": {"total": stats["tracks"]}, - "artists": {"total": stats["artists"]}, - "albums": {"total": stats["albums"]}, - "music": {"hours": stats["music_duration"]}, - }, - "usage": { - "favorites": {"tracks": {"total": stats["track_favorites"]}}, - "listenings": {"total": stats["listenings"]}, - "downloads": {"total": stats["downloads"]}, - }, - "supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS, - "allowList": {"enabled": False, "domains": None}, - "reportTypes": [ - { - "type": "takedown_request", - "label": "Takedown request", - "anonymous": True, - }, - { - "type": "invalid_metadata", - "label": "Invalid metadata", - "anonymous": False, - }, - { - "type": "illegal_content", - "label": "Illegal content", - "anonymous": False, - }, - { - "type": "offensive_content", - "label": "Offensive content", - "anonymous": False, - }, - {"type": "other", "label": "Other", "anonymous": True}, - ], - "funkwhaleSupportMessageEnabled": preferences[ - "instance__funkwhale_support_message_enabled" - ], - "instanceSupportMessage": preferences["instance__support_message"], - "endpoints": { - "knownNodes": federation_utils.full_url( - reverse("api:v1:federation:domains-list") - ), - "libraries": federation_utils.full_url( - reverse("federation:index:index-libraries") - ), - "channels": federation_utils.full_url( - reverse("federation:index:index-channels") - ), - }, + "services": OrderedDict([("inbound", []), ("outbound", [])]), + "openRegistrations": False, + "usage": { + "users": OrderedDict( + [("total", 0), ("activeHalfyear", 0), ("activeMonth", 0)] + ) }, - } - assert nodeinfo.get() == expected - - -def test_nodeinfo_dump_stats_disabled(preferences, mocker): - preferences["instance__nodeinfo_stats_enabled"] = False - preferences["federation__public_index"] = False - preferences["moderation__unauthenticated_report_types"] = [ - "takedown_request", - "other", - ] - - expected = { - "version": "2.0", - "software": {"name": "funkwhale", "version": funkwhale_api.__version__}, - "protocols": ["activitypub"], - "services": {"inbound": [], "outbound": []}, - "openRegistrations": preferences["users__registration_enabled"], - "usage": {"users": {"total": 0, "activeHalfyear": 0, "activeMonth": 0}}, "metadata": { - "actorId": actors.get_service_actor().fid, - "private": preferences["instance__nodeinfo_private"], - "shortDescription": preferences["instance__short_description"], - "longDescription": preferences["instance__long_description"], - "nodeName": preferences["instance__name"], - "rules": preferences["instance__rules"], - "contactEmail": preferences["instance__contact_email"], - "defaultUploadQuota": preferences["users__upload_quota"], - "terms": preferences["instance__terms"], + "actorId": "https://test.federation/federation/actors/service", + "private": False, + "shortDescription": "", + "longDescription": "", + "rules": "", + "contactEmail": "", + "terms": "", + "nodeName": "", "banner": None, + "defaultUploadQuota": 1000, "library": { - "federationEnabled": preferences["federation__enabled"], - "anonymousCanListen": not preferences[ - "common__api_authentication_required" - ], + "federationEnabled": True, + "anonymousCanListen": False, + "tracks": OrderedDict([("total", 0)]), + "artists": OrderedDict([("total", 0)]), + "albums": OrderedDict([("total", 0)]), + "music": OrderedDict([("hours", 0)]), }, - "supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS, + "supportedUploadExtensions": [ + "aac", + "aif", + "aiff", + "flac", + "m4a", + "mp3", + "ogg", + "opus", + ], "allowList": {"enabled": False, "domains": None}, "reportTypes": [ - { - "type": "takedown_request", - "label": "Takedown request", - "anonymous": True, - }, - { - "type": "invalid_metadata", - "label": "Invalid metadata", - "anonymous": False, - }, - { - "type": "illegal_content", - "label": "Illegal content", - "anonymous": False, - }, - { - "type": "offensive_content", - "label": "Offensive content", - "anonymous": False, - }, - {"type": "other", "label": "Other", "anonymous": True}, + OrderedDict( + [ + ("type", "takedown_request"), + ("label", "Takedown request"), + ("anonymous", True), + ] + ), + OrderedDict( + [ + ("type", "invalid_metadata"), + ("label", "Invalid metadata"), + ("anonymous", False), + ] + ), + OrderedDict( + [ + ("type", "illegal_content"), + ("label", "Illegal content"), + ("anonymous", True), + ] + ), + OrderedDict( + [ + ("type", "offensive_content"), + ("label", "Offensive content"), + ("anonymous", False), + ] + ), + OrderedDict( + [("type", "other"), ("label", "Other"), ("anonymous", False)] + ), ], - "funkwhaleSupportMessageEnabled": preferences[ - "instance__funkwhale_support_message_enabled" - ], - "instanceSupportMessage": preferences["instance__support_message"], - "endpoints": {"knownNodes": None, "libraries": None, "channels": None}, + "funkwhaleSupportMessageEnabled": True, + "instanceSupportMessage": "", + "endpoints": OrderedDict( + [("knownNodes", None), ("channels", None), ("libraries", None)] + ), + "usage": { + "favorites": OrderedDict([("tracks", {"total": 0})]), + "listenings": OrderedDict([("total", 0)]), + "downloads": OrderedDict([("total", 0)]), + }, }, } - assert nodeinfo.get() == expected - -@pytest.mark.parametrize( - "enabled, public, expected", - [ - (True, True, {"enabled": True, "domains": ["allowed.example"]}), - (True, False, {"enabled": True, "domains": None}), - (False, False, {"enabled": False, "domains": None}), - ], -) -def test_nodeinfo_allow_list_enabled(preferences, factories, enabled, public, expected): - preferences["moderation__allow_list_enabled"] = enabled - preferences["moderation__allow_list_public"] = public - factories["federation.Domain"](name="allowed.example", allowed=True) - factories["federation.Domain"](allowed=False) - factories["federation.Domain"](allowed=None) - - assert nodeinfo.get()["metadata"]["allowList"] == expected + assert response.data == expected diff --git a/api/tests/instance/test_views.py b/api/tests/instance/test_views.py index 4bc9c296a..005924990 100644 --- a/api/tests/instance/test_views.py +++ b/api/tests/instance/test_views.py @@ -5,15 +5,12 @@ from django.urls import reverse from funkwhale_api.federation import utils as federation_utils -def test_nodeinfo_endpoint(db, api_client, mocker): - payload = {"test": "test"} - mocker.patch("funkwhale_api.instance.nodeinfo.get", return_value=payload) +def test_nodeinfo_endpoint(db, api_client): url = reverse("api:v1:instance:nodeinfo-2.0") response = api_client.get(url) ct = "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8" # noqa assert response.status_code == 200 assert response["Content-Type"] == ct - assert response.data == payload def test_settings_only_list_public_settings(db, api_client, preferences): diff --git a/changes/changelog.d/nodeinfo.enhancement b/changes/changelog.d/nodeinfo.enhancement new file mode 100644 index 000000000..d125fedf6 --- /dev/null +++ b/changes/changelog.d/nodeinfo.enhancement @@ -0,0 +1 @@ +Refactor node info endpoint to use proper serializers \ No newline at end of file