Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mastodon API node mixin #209

Merged
merged 1 commit into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"