Skip to content

Commit

Permalink
Mastodon API node mixin (#209)
Browse files Browse the repository at this point in the history
  • Loading branch information
steve-bate authored Jul 8, 2024
1 parent 77bf4e6 commit adec0ed
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 25 deletions.
22 changes: 22 additions & 0 deletions src/feditest/nodedrivers/mastodon/manual.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

from typing import Any

from feditest import nodedriver
from feditest.nodedrivers.mastodon.mixin import MastodonApiMixin
from feditest.protocols import Node, NodeDriver
from feditest.protocols.activitypub import ActivityPubNode


class MastodonManualNode(MastodonApiMixin, ActivityPubNode):
...

@nodedriver
class MastodonManualNodeDriver(NodeDriver):
"""
Expects a manually-provisioned Mastodon node.
"""

def _provision_node(self, rolename: str, parameters: dict[str, Any]) -> Node:
return MastodonManualNode(rolename, parameters, self)

def _unprovision_node(self, node: Node) -> None: ...
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

import httpx

from feditest import AssertionFailure, InteropLevel, SpecLevel, nodedriver
from feditest.protocols import Node, NodeDriver
from feditest import AssertionFailure, InteropLevel, SpecLevel
from feditest.protocols import NodeDriver
from feditest.protocols.activitypub import ActivityPubNode

# This kludge is needed because the node driver loader
Expand All @@ -26,6 +26,8 @@
Mastodon = mastodon_api.Mastodon
finally:
sys.modules["mastodon"] = m
else:
from mastodon import Mastodon


def _dereference(uri: str) -> dict:
Expand Down Expand Up @@ -62,7 +64,7 @@ def is_mastodon(self):
return self.api is not None


class MastodonNode(ActivityPubNode):
class MastodonApiMixin:
"""
Supported Parameters:
{
Expand Down Expand Up @@ -108,7 +110,8 @@ def __init__(
for role_name, role_params in self._param("actors", "roles").items()
]
self.default_actor = next(
a for a in self.actors if a.role == self._param("actors", "default_role")
a for a in self.actors
if a.role == self._param("actors", "default_role")
)
self.actors_by_uri = {a.uri: a for a in self.actors}
self.actors_by_role = {a.role: a for a in self.actors}
Expand Down Expand Up @@ -310,14 +313,6 @@ def follow(self, actor1: Actor | str, actor2: Actor | str):
self._ensure_actor(actor2).uri,
)


@nodedriver
class MastodonNodeDriver(NodeDriver):
"""
Knows how to instantiate Mastodon via UBOS.
"""

def _provision_node(self, rolename: str, parameters: dict[str, Any]) -> Node:
return MastodonNode(rolename, parameters, self)

def _unprovision_node(self, node: Node) -> None: ...
@property
def server_version(self):
return self.default_actor.api.instance()["version"]
16 changes: 14 additions & 2 deletions src/feditest/nodedrivers/mastodon/ubos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,28 @@
from typing import Any

from feditest import nodedriver
from feditest.protocols import Node
from feditest.nodedrivers.mastodon.mixin import MastodonApiMixin
from feditest.protocols import Node, NodeDriver
from feditest.protocols.fediverse import FediverseNode
from feditest.ubos import UbosNodeDriver


class MastodonUbosNode(FediverseNode):
class MastodonUbosNode(MastodonApiMixin, FediverseNode):
"""
A Node running Mastodon, instantiated with UBOS.
"""

def __init__(self, rolename: str, parameters: dict[str, Any], node_driver: NodeDriver):
# TODO Automatic actor provisioning
# Use parameters to determine which actors to provision
# with UBOS with which information
# Copy and modify the parameters for usage by the MastodonApiMixin
# Must invoke base class constructors explicitly since Python will
# normally only call the first __init__ it finds in the MRO.
# super().__init__(rolename, parameters, node_driver)
MastodonApiMixin.__init__(self, rolename, parameters, node_driver)
FediverseNode.__init__(self, rolename, parameters, node_driver)

def obtain_actor_document_uri(self, actor_rolename: str | None = None) -> str:
return f"https://{self.hostname}/users/{self.parameter('adminid') }"

Expand Down
20 changes: 12 additions & 8 deletions tests/test_mastodon_node.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import json
import os
import re
from datetime import datetime

import pytest

# Needed to override some of the static behaviors for test support
os.environ["ALLOW_EXTERNAL_NODE_DRIVERS"] = "1"
from feditest.nodedrivers.mastodon.api import MastodonNode # noqa
from feditest.nodedrivers.mastodon.manual import MastodonManualNode # noqa

# To run these tests, you must create a file mastodon_parameters.json
# in this test directory with parameters like the following.
Expand Down Expand Up @@ -37,17 +38,17 @@ def node():
cwd = os.path.dirname(__file__)
with open(os.path.join(cwd, "mastodon_parameters.json")) as fp:
parameters = json.load(fp)
return MastodonNode("client", parameters, None)
return MastodonManualNode("client", parameters, None)


@pytest.fixture(autouse=True, scope="session")
def session_setup(node: MastodonNode):
def session_setup(node: MastodonManualNode):
node.delete_follows()
node.delete_statuses()


@pytest.fixture(scope="session")
def note_uri(node: MastodonNode):
def note_uri(node: MastodonManualNode):
note_uri = node.make_create_note(None, f"testing make_create_note {datetime.now()}")
node.wait_for_object_in_inbox(None, note_uri)
return note_uri
Expand All @@ -56,22 +57,25 @@ def note_uri(node: MastodonNode):
# make_create_node is implied by other tests


def test_announce_note(node: MastodonNode, note_uri: str):
def test_announce_note(node: MastodonManualNode, note_uri: str):
announce_uri = node.make_announce_object(None, note_uri)
print(announce_uri)


def test_reply_note(node: MastodonNode, note_uri: str):
def test_reply_note(node: MastodonManualNode, note_uri: str):
reply_uri = node.make_reply(None, note_uri, f"test_reply_note {datetime.now()}")
print(reply_uri)


def test_follow_local(node: MastodonNode):
def test_follow_local(node: MastodonManualNode):
node.follow("primary_actor", "secondary_actor")


def test_follow_remote(node: MastodonNode):
def test_follow_remote(node: MastodonManualNode):
if "external_actor" in node.actors_by_role:
node.follow("primary_actor", "external_actor")
else:
pytest.skip("No external actor is configured")

def test_server_version(node: MastodonManualNode):
assert re.match(r'\d+\.\d+\.\d+', node.server_version), "Invalid version"

0 comments on commit adec0ed

Please sign in to comment.