Exemple #1
0
def test_report_created_signal_sends_email_to_mods(factories, mailoutbox,
                                                   settings):
    mod1 = factories["users.User"](permission_moderation=True)
    mod2 = factories["users.User"](permission_moderation=True)
    # inactive, so no email
    factories["users.User"](permission_moderation=True, is_active=False)
    # no moderation permission, so no email
    factories["users.User"]()

    report = factories["moderation.Report"]()

    tasks.send_new_report_email_to_moderators(report_id=report.pk)

    detail_url = federation_utils.full_url(
        "/manage/moderation/reports/{}".format(report.uuid))
    unresolved_reports_url = federation_utils.full_url(
        "/manage/moderation/reports?q=resolved:no")
    assert len(mailoutbox) == 2
    for i, mod in enumerate([mod1, mod2]):
        m = mailoutbox[i]
        assert m.subject == "[{} moderation - {}] New report from {}".format(
            settings.FUNKWHALE_HOSTNAME,
            report.get_type_display(),
            report.submitter.full_username,
        )
        assert report.summary in m.body
        assert report.target._meta.verbose_name.title() in m.body
        assert str(report.target) in m.body
        assert report.target.get_absolute_url() in m.body
        assert report.target.get_moderation_url() in m.body
        assert detail_url in m.body
        assert unresolved_reports_url in m.body
        assert list(m.to) == [mod.email]
Exemple #2
0
def test_attachment_serializer_remote_file(factories, to_api_date):
    attachment = factories["common.Attachment"](file=None)
    proxy_url = reverse("api:v1:attachments-proxy", kwargs={"uuid": attachment.uuid})
    expected = {
        "uuid": str(attachment.uuid),
        "size": attachment.size,
        "mimetype": attachment.mimetype,
        "creation_date": to_api_date(attachment.creation_date),
        # everything is the same, except for the urls field because:
        #  - the file isn't available on the local pod
        #  - we need to return different URLs so that the client can trigger
        #    a fetch and get redirected to the desired version
        #
        "urls": {
            "source": attachment.url,
            "original": federation_utils.full_url(proxy_url + "?next=original"),
            "medium_square_crop": federation_utils.full_url(
                proxy_url + "?next=medium_square_crop"
            ),
            "large_square_crop": federation_utils.full_url(
                proxy_url + "?next=large_square_crop"
            ),
        },
    }

    serializer = serializers.AttachmentSerializer(attachment)

    assert serializer.data == expected
Exemple #3
0
def get_actor_data(username, **kwargs):
    slugified_username = federation_utils.slugify_username(username)
    domain = kwargs.get("domain")
    if not domain:
        domain = federation_models.Domain.objects.get_or_create(
            name=settings.FEDERATION_HOSTNAME)[0]
    return {
        "preferred_username":
        slugified_username,
        "domain":
        domain,
        "type":
        "Person",
        "name":
        kwargs.get("name", username),
        "summary":
        kwargs.get("summary"),
        "manually_approves_followers":
        False,
        "fid":
        federation_utils.full_url(
            reverse(
                "federation:actors-detail",
                kwargs={"preferred_username": slugified_username},
            )),
        "shared_inbox_url":
        federation_models.get_shared_inbox_url(),
        "inbox_url":
        federation_utils.full_url(
            reverse(
                "federation:actors-inbox",
                kwargs={"preferred_username": slugified_username},
            )),
        "outbox_url":
        federation_utils.full_url(
            reverse(
                "federation:actors-outbox",
                kwargs={"preferred_username": slugified_username},
            )),
        "followers_url":
        federation_utils.full_url(
            reverse(
                "federation:actors-followers",
                kwargs={"preferred_username": slugified_username},
            )),
        "following_url":
        federation_utils.full_url(
            reverse(
                "federation:actors-following",
                kwargs={"preferred_username": slugified_username},
            )),
    }
Exemple #4
0
def test_can_create_track_from_api(artists, albums, tracks, mocker, db):
    mocker.patch(
        "funkwhale_api.musicbrainz.api.artists.get",
        return_value=artists["get"]["adhesive_wombat"],
    )
    mocker.patch(
        "funkwhale_api.musicbrainz.api.releases.get",
        return_value=albums["get"]["marsupial"],
    )
    mocker.patch(
        "funkwhale_api.musicbrainz.api.recordings.search",
        return_value=tracks["search"]["8bitadventures"],
    )
    track = models.Track.create_from_api(query="8-bit adventure")
    data = models.Track.api.search(query="8-bit adventure")["recording-list"][0]
    assert int(data["ext:score"]) == 100
    assert data["id"] == "9968a9d6-8d92-4051-8f76-674e157b6eed"
    assert track.mbid == data["id"]
    assert track.artist.pk is not None
    assert str(track.artist.mbid) == "62c3befb-6366-4585-b256-809472333801"
    assert track.artist.name == "Adhesive Wombat"
    assert str(track.album.mbid) == "a50d2a81-2a50-484d-9cb4-b9f6833f583e"
    assert track.album.title == "Marsupial Madness"
    assert track.fid == federation_utils.full_url(
        "/federation/music/tracks/{}".format(track.uuid)
    )
Exemple #5
0
 def get_absolute_url(self):
     suffix = self.uuid
     if self.actor.is_local:
         suffix = self.actor.preferred_username
     else:
         suffix = self.actor.full_username
     return federation_utils.full_url("/channels/{}".format(suffix))
Exemple #6
0
 def download_url_medium_square_crop(self):
     if self.file:
         return utils.media_url(self.file.crop["200x200"].url)
     proxy_url = reverse("api:v1:attachments-proxy",
                         kwargs={"uuid": self.uuid})
     return federation_utils.full_url(proxy_url +
                                      "?next=medium_square_crop")
Exemple #7
0
    def get_federation_id(self):
        if self.fid:
            return self.fid

        return federation_utils.full_url(
            reverse("federation:music:uploads-detail",
                    kwargs={"uuid": self.uuid}))
def test_activity_pub_album_serializer_to_ap(factories):
    album = factories["music.Album"]()

    expected = {
        "@context":
        serializers.AP_CONTEXT,
        "type":
        "Album",
        "id":
        album.fid,
        "name":
        album.title,
        "cover": {
            "type": "Link",
            "mediaType": "image/jpeg",
            "href": utils.full_url(album.cover.url),
        },
        "musicbrainzId":
        album.mbid,
        "published":
        album.creation_date.isoformat(),
        "released":
        album.release_date.isoformat(),
        "artists": [
            serializers.ArtistSerializer(album.artist,
                                         context={
                                             "include_ap_context": False
                                         }).data
        ],
    }
    serializer = serializers.AlbumSerializer(album)

    assert serializer.data == expected
Exemple #9
0
    def channels(self, request, *args, **kwargs):
        actors = (models.Actor.objects.local().exclude(
            channel=None).order_by("channel__creation_date").prefetch_related(
                "channel__attributed_to",
                "channel__artist",
                "channel__artist__description",
                "channel__artist__attachment_cover",
            ))
        conf = {
            "id":
            federation_utils.full_url(
                reverse("federation:index:index-channels")),
            "items":
            actors,
            "item_serializer":
            serializers.ActorSerializer,
            "page_size":
            100,
            "actor":
            None,
        }
        return get_collection_response(
            conf=conf,
            querystring=request.GET,
            collection_serializer=serializers.IndexSerializer(conf),
        )

        return response.Response({}, status=200)
Exemple #10
0
def test_oembed_channel(factories, no_api_auth, api_client, settings):
    settings.FUNKWHALE_URL = "http://test"
    settings.FUNKWHALE_EMBED_URL = "http://embed"
    channel = factories["audio.Channel"](artist__with_cover=True)
    artist = channel.artist
    url = reverse("api:v1:oembed")
    obj_url = "https://test.com/channels/{}".format(channel.uuid)
    iframe_src = "http://embed?type=channel&id={}".format(channel.uuid)
    expected = {
        "version":
        "1.0",
        "type":
        "rich",
        "provider_name":
        settings.APP_NAME,
        "provider_url":
        settings.FUNKWHALE_URL,
        "height":
        400,
        "width":
        600,
        "title":
        artist.name,
        "description":
        artist.name,
        "thumbnail_url":
        federation_utils.full_url(
            artist.attachment_cover.file.crop["200x200"].url),
        "thumbnail_height":
        200,
        "thumbnail_width":
        200,
        "html":
        '<iframe width="600" height="400" scrolling="no" frameborder="no" src="{}"></iframe>'
        .format(iframe_src),
        "author_name":
        artist.name,
        "author_url":
        federation_utils.full_url(
            utils.spa_reverse("channel_detail", kwargs={"uuid":
                                                        channel.uuid})),
    }

    response = api_client.get(url, {"url": obj_url, "format": "json"})

    assert response.data == expected
Exemple #11
0
def test_channel_get_rss_url_local(factories):
    channel = factories["audio.Channel"](artist__local=True)
    expected = federation_utils.full_url(
        reverse(
            "api:v1:channels-rss",
            kwargs={"composite": channel.actor.preferred_username},
        ))
    assert channel.get_rss_url() == expected
Exemple #12
0
def media_url(path):
    if settings.MEDIA_URL.startswith(
            "http://") or settings.MEDIA_URL.startswith("https://"):
        return join_url(settings.MEDIA_URL, path)

    from funkwhale_api.federation import utils as federation_utils

    return federation_utils.full_url(path)
Exemple #13
0
    def get_federation_id(self):
        if self.fid:
            return self.fid

        return federation_utils.full_url(
            reverse(
                "federation:music:{}-detail".format(self.federation_namespace),
                kwargs={"uuid": self.uuid},
            ))
Exemple #14
0
    def get_rss_url(self):
        if not self.artist.is_local or self.is_external_rss:
            return self.rss_url

        return federation_utils.full_url(
            reverse(
                "api:v1:channels-rss",
                kwargs={"composite": self.actor.preferred_username},
            ))
Exemple #15
0
def serve_spa(request):
    html = get_spa_html(settings.FUNKWHALE_SPA_HTML_ROOT)
    head, tail = html.split("</head>", 1)
    if settings.FUNKWHALE_SPA_REWRITE_MANIFEST:
        new_url = (settings.FUNKWHALE_SPA_REWRITE_MANIFEST_URL
                   or federation_utils.full_url(
                       urls.reverse("api:v1:instance:spa-manifest")))
        title = preferences.get("instance__name")
        if title:
            head = replace_title(head, title)
        head = replace_manifest_url(head, new_url)

    if not preferences.get("common__api_authentication_required"):
        try:
            request_tags = get_request_head_tags(request) or []
        except urls.exceptions.Resolver404:
            # we don't have any custom tags for this route
            request_tags = []
    else:
        # API is not open, we don't expose any custom data
        request_tags = []
    default_tags = get_default_head_tags(request.path)
    unique_attributes = ["name", "property"]

    final_tags = request_tags
    skip = []

    for t in final_tags:
        for attr in unique_attributes:
            if attr in t:
                skip.append(t[attr])
    for t in default_tags:
        existing = False
        for attr in unique_attributes:
            if t.get(attr) in skip:
                existing = True
                break
        if not existing:
            final_tags.append(t)

    # let's inject our meta tags in the HTML
    head += "\n" + "\n".join(render_tags(final_tags)) + "\n</head>"
    css = get_custom_css() or ""
    if css:
        # We add the style add the end of the body to ensure it has the highest
        # priority (since it will come after other stylesheets)
        body, tail = tail.split("</body>", 1)
        css = "<style>{}</style>".format(css)
        tail = body + "\n" + css + "\n</body>" + tail

    # set a csrf token so that visitor can login / query API if needed
    token = csrf.get_token(request)
    response = http.HttpResponse(head + tail)
    response.set_cookie("csrftoken", token, max_age=None)
    return response
Exemple #16
0
def notify_mods_signup_request_pending(obj):
    moderators = get_moderators()
    submitter_repr = obj.submitter.preferred_username
    subject = "[{} moderation] New sign-up request from {}".format(
        settings.FUNKWHALE_HOSTNAME, submitter_repr
    )
    detail_url = federation_utils.full_url(
        "/manage/moderation/requests/{}".format(obj.uuid)
    )
    unresolved_requests_url = federation_utils.full_url(
        "/manage/moderation/requests?q=status:pending"
    )
    unresolved_requests = models.UserRequest.objects.filter(status="pending").count()
    body = [
        "{} wants to register on your pod. You need to review their request before they can use the service.".format(
            submitter_repr
        ),
        "",
        "- To handle this request, please visit {}".format(detail_url),
        "- To view all unresolved requests (currently {}), please visit {}".format(
            unresolved_requests, unresolved_requests_url
        ),
        "",
        "—",
        "",
        "You are receiving this email because you are a moderator for {}.".format(
            settings.FUNKWHALE_HOSTNAME
        ),
    ]

    for moderator in moderators:
        if not moderator.email:
            logger.warning("Moderator %s has no email configured", moderator.username)
            continue
        mail.send_mail(
            subject,
            message="\n".join(body),
            recipient_list=[moderator.email],
            from_email=settings.DEFAULT_FROM_EMAIL,
        )
Exemple #17
0
def test_attachment_serializer_existing_file(factories, to_api_date):
    attachment = factories["common.Attachment"]()
    expected = {
        "uuid":
        str(attachment.uuid),
        "size":
        attachment.size,
        "mimetype":
        attachment.mimetype,
        "creation_date":
        to_api_date(attachment.creation_date),
        "urls": {
            "source":
            attachment.url,
            "original":
            federation_utils.full_url(attachment.file.url),
            "medium_square_crop":
            federation_utils.full_url(attachment.file.crop["200x200"].url),
        },
        # XXX: BACKWARD COMPATIBILITY
        "original":
        federation_utils.full_url(attachment.file.url),
        "medium_square_crop":
        federation_utils.full_url(attachment.file.crop["200x200"].url),
        "small_square_crop":
        federation_utils.full_url(attachment.file.crop["200x200"].url),
        "square_crop":
        federation_utils.full_url(attachment.file.crop["200x200"].url),
    }

    serializer = serializers.AttachmentSerializer(attachment)

    assert serializer.data == expected
Exemple #18
0
def rss_serialize_item(upload):
    data = {
        "title": [{"value": upload.track.title}],
        "itunes:title": [{"value": upload.track.title}],
        "guid": [{"cdata_value": str(upload.uuid), "isPermalink": "false"}],
        "pubDate": [{"value": rfc822_date(upload.creation_date)}],
        "itunes:duration": [{"value": rss_duration(upload.duration)}],
        "itunes:explicit": [{"value": "no"}],
        "itunes:episodeType": [{"value": "full"}],
        "itunes:season": [{"value": upload.track.disc_number or 1}],
        "itunes:episode": [{"value": upload.track.position or 1}],
        "link": [{"value": federation_utils.full_url(upload.track.get_absolute_url())}],
        "enclosure": [
            {
                # we enforce MP3, since it's the only format supported everywhere
                "url": federation_utils.full_url(upload.get_listen_url(to="mp3")),
                "length": upload.size or 0,
                "type": "audio/mpeg",
            }
        ],
    }
    if upload.track.description:
        data["itunes:subtitle"] = [{"value": upload.track.description.truncate(255)}]
        data["itunes:summary"] = [{"cdata_value": upload.track.description.rendered}]
        data["description"] = [{"value": upload.track.description.as_plain_text}]

    if upload.track.attachment_cover:
        data["itunes:image"] = [
            {"href": upload.track.attachment_cover.download_url_original}
        ]

    tagged_items = getattr(upload.track, "_prefetched_tagged_items", [])
    if tagged_items:
        data["itunes:keywords"] = [
            {"value": " ".join([ti.tag.name for ti in tagged_items])}
        ]

    return data
Exemple #19
0
 def get(self, request, *args, **kwargs):
     existing_manifest = middleware.get_spa_file(
         settings.FUNKWHALE_SPA_HTML_ROOT, "manifest.json")
     parsed_manifest = json.loads(existing_manifest)
     parsed_manifest["short_name"] = settings.APP_NAME
     parsed_manifest["start_url"] = federation_utils.full_url("/")
     instance_name = preferences.get("instance__name")
     if instance_name:
         parsed_manifest["short_name"] = instance_name
         parsed_manifest["name"] = instance_name
     instance_description = preferences.get("instance__short_description")
     if instance_description:
         parsed_manifest["description"] = instance_description
     return Response(parsed_manifest, status=200)
Exemple #20
0
def get_actor_data(user):
    username = federation_utils.slugify_username(user.username)
    return {
        "preferred_username":
        username,
        "domain":
        settings.FEDERATION_HOSTNAME,
        "type":
        "Person",
        "name":
        user.username,
        "manually_approves_followers":
        False,
        "fid":
        federation_utils.full_url(
            reverse("federation:actors-detail",
                    kwargs={"preferred_username": username})),
        "shared_inbox_url":
        federation_models.get_shared_inbox_url(),
        "inbox_url":
        federation_utils.full_url(
            reverse("federation:actors-inbox",
                    kwargs={"preferred_username": username})),
        "outbox_url":
        federation_utils.full_url(
            reverse("federation:actors-outbox",
                    kwargs={"preferred_username": username})),
        "followers_url":
        federation_utils.full_url(
            reverse("federation:actors-followers",
                    kwargs={"preferred_username": username})),
        "following_url":
        federation_utils.full_url(
            reverse("federation:actors-following",
                    kwargs={"preferred_username": username})),
    }
Exemple #21
0
def test_signup_request_pending_sends_email_to_mods(factories, mailoutbox,
                                                    settings):
    mod1 = factories["users.User"](permission_moderation=True)
    mod2 = factories["users.User"](permission_moderation=True)

    signup_request = factories["moderation.UserRequest"](signup=True)

    tasks.user_request_handle(user_request_id=signup_request.pk,
                              new_status="pending")

    detail_url = federation_utils.full_url(
        "/manage/moderation/requests/{}".format(signup_request.uuid))
    unresolved_requests_url = federation_utils.full_url(
        "/manage/moderation/requests?q=status:pending")
    assert len(mailoutbox) == 2
    for i, mod in enumerate([mod1, mod2]):
        m = mailoutbox[i]
        assert m.subject == "[{} moderation] New sign-up request from {}".format(
            settings.FUNKWHALE_HOSTNAME,
            signup_request.submitter.preferred_username,
        )
        assert detail_url in m.body
        assert unresolved_requests_url in m.body
        assert list(m.to) == [mod.email]
Exemple #22
0
def test_can_create_artist_from_api(artists, mocker, db):
    mocker.patch(
        "musicbrainzngs.search_artists",
        return_value=artists["search"]["adhesive_wombat"],
    )
    artist = models.Artist.create_from_api(query="Adhesive wombat")
    data = models.Artist.api.search(query="Adhesive wombat")["artist-list"][0]

    assert int(data["ext:score"]), 100
    assert data["id"], "62c3befb-6366-4585-b256-809472333801"
    assert artist.mbid, data["id"]
    assert artist.name, "Adhesive Wombat"
    assert artist.fid == federation_utils.full_url(
        "/federation/music/artists/{}".format(artist.uuid)
    )
Exemple #23
0
def test_rewrite_manifest_json_url_rewrite_default_url(mocker, settings):
    settings.FUNKWHALE_SPA_REWRITE_MANIFEST = True
    settings.FUNKWHALE_SPA_REWRITE_MANIFEST_URL = None
    spa_html = "<html><head><link href=/manifest.json rel=manifest></head></html>"
    expected_url = federation_utils.full_url(
        reverse("api:v1:instance:spa-manifest"))
    request = mocker.Mock(path="/")
    mocker.patch.object(middleware, "get_spa_html", return_value=spa_html)
    mocker.patch.object(
        middleware,
        "get_default_head_tags",
        return_value=[],
    )
    response = middleware.serve_spa(request)

    assert response.status_code == 200
    expected_html = '<html><head><link rel=manifest href="{}">\n\n</head></html>'.format(
        expected_url)
    assert response.content == expected_html.encode()
def test_activity_pub_audio_serializer_to_ap(factories):
    upload = factories["music.Upload"](mimetype="audio/mp3",
                                       bitrate=42,
                                       duration=43,
                                       size=44)
    expected = {
        "@context":
        serializers.AP_CONTEXT,
        "type":
        "Audio",
        "id":
        upload.fid,
        "name":
        upload.track.full_name,
        "published":
        upload.creation_date.isoformat(),
        "updated":
        upload.modification_date.isoformat(),
        "duration":
        upload.duration,
        "bitrate":
        upload.bitrate,
        "size":
        upload.size,
        "url": {
            "href": utils.full_url(upload.listen_url),
            "type": "Link",
            "mediaType": "audio/mp3",
        },
        "library":
        upload.library.fid,
        "track":
        serializers.TrackSerializer(upload.track,
                                    context={
                                        "include_ap_context": False
                                    }).data,
    }

    serializer = serializers.UploadSerializer(upload)

    assert serializer.data == expected
Exemple #25
0
def test_rss_channel_serializer_placeholder_image(factories):
    description = factories["common.Content"]()
    channel = factories["audio.Channel"](
        artist__set_tags=["pop", "rock"],
        artist__description=description,
        artist__attachment_cover=None,
    )
    setattr(
        channel.artist,
        "_prefetched_tagged_items",
        channel.artist.tagged_items.order_by("tag__name"),
    )

    expected = [{
        "href":
        federation_utils.full_url(
            static("images/podcasts-cover-placeholder.png"))
    }]

    assert serializers.rss_serialize_channel(
        channel)["itunes:image"] == expected
Exemple #26
0
def test_approved_request_sends_email_to_submitter_and_set_active(
        factories, mailoutbox, settings):
    user = factories["users.User"](is_active=False)
    actor = user.create_actor()
    signup_request = factories["moderation.UserRequest"](signup=True,
                                                         submitter=actor,
                                                         status="approved")

    tasks.user_request_handle(user_request_id=signup_request.pk,
                              new_status="approved")

    user.refresh_from_db()

    assert user.is_active is True
    assert len(mailoutbox) == 1
    m = mailoutbox[-1]
    login_url = federation_utils.full_url("/login")
    assert m.subject == "Welcome to {}, {}!".format(
        settings.FUNKWHALE_HOSTNAME,
        signup_request.submitter.preferred_username,
    )
    assert login_url in m.body
    assert list(m.to) == [user.email]
Exemple #27
0
def notify_submitter_signup_request_approved(user_request):
    submitter_repr = user_request.submitter.preferred_username
    submitter_email = user_request.submitter.user.email
    if not submitter_email:
        logger.warning("User %s has no email configured", submitter_repr)
        return
    subject = "Welcome to {}, {}!".format(settings.FUNKWHALE_HOSTNAME, submitter_repr)
    login_url = federation_utils.full_url("/login")
    body = [
        "Hi {} and welcome,".format(submitter_repr),
        "",
        "Our moderation team has approved your account request and you can now start "
        "using the service. Please visit {} to get started.".format(login_url),
        "",
        "Before your first login, you may need to verify your email address if you didn't already.",
    ]

    mail.send_mail(
        subject,
        message="\n".join(body),
        recipient_list=[submitter_email],
        from_email=settings.DEFAULT_FROM_EMAIL,
    )
Exemple #28
0
def test_can_create_album_from_api(artists, albums, mocker, db):
    mocker.patch(
        "funkwhale_api.musicbrainz.api.releases.search",
        return_value=albums["search"]["hypnotize"],
    )
    mocker.patch(
        "funkwhale_api.musicbrainz.api.artists.get", return_value=artists["get"]["soad"]
    )
    album = models.Album.create_from_api(
        query="Hypnotize", artist="system of a down", type="album"
    )
    data = models.Album.api.search(
        query="Hypnotize", artist="system of a down", type="album"
    )["release-list"][0]

    assert album.mbid, data["id"]
    assert album.title, "Hypnotize"
    assert album.release_date, datetime.date(2005, 1, 1)
    assert album.artist.name, "System of a Down"
    assert album.artist.mbid, data["artist-credit"][0]["artist"]["id"]
    assert album.fid == federation_utils.full_url(
        "/federation/music/albums/{}".format(album.uuid)
    )
Exemple #29
0
    def libraries(self, request, *args, **kwargs):
        libraries = (music_models.Library.objects.local().filter(
            channel=None, privacy_level="everyone").prefetch_related(
                "actor").order_by("creation_date"))
        conf = {
            "id":
            federation_utils.full_url(
                reverse("federation:index:index-libraries")),
            "items":
            libraries,
            "item_serializer":
            serializers.LibrarySerializer,
            "page_size":
            100,
            "actor":
            None,
        }
        return get_collection_response(
            conf=conf,
            querystring=request.GET,
            collection_serializer=serializers.IndexSerializer(conf),
        )

        return response.Response({}, status=200)
Exemple #30
0
def test_creating_actor_from_user(factories, settings):
    user = factories["users.User"](username="******")
    actor = models.create_actor(user)

    assert actor.preferred_username == "Hello_M_world"  # slugified
    assert actor.domain.pk == settings.FEDERATION_HOSTNAME
    assert actor.type == "Person"
    assert actor.name == user.username
    assert actor.manually_approves_followers is False
    assert actor.fid == federation_utils.full_url(
        reverse(
            "federation:actors-detail",
            kwargs={"preferred_username": actor.preferred_username},
        ))
    assert actor.shared_inbox_url == federation_utils.full_url(
        reverse("federation:shared-inbox"))
    assert actor.inbox_url == federation_utils.full_url(
        reverse(
            "federation:actors-inbox",
            kwargs={"preferred_username": actor.preferred_username},
        ))
    assert actor.outbox_url == federation_utils.full_url(
        reverse(
            "federation:actors-outbox",
            kwargs={"preferred_username": actor.preferred_username},
        ))
    assert actor.followers_url == federation_utils.full_url(
        reverse(
            "federation:actors-followers",
            kwargs={"preferred_username": actor.preferred_username},
        ))
    assert actor.following_url == federation_utils.full_url(
        reverse(
            "federation:actors-following",
            kwargs={"preferred_username": actor.preferred_username},
        ))