diff --git a/src/charm_types.py b/src/charm_types.py index bed51aa..fe11c11 100644 --- a/src/charm_types.py +++ b/src/charm_types.py @@ -7,11 +7,24 @@ import re import typing +from abc import ABC, abstractmethod import ops from pydantic import BaseModel, Field, ValidationError, validator +class ReconcilingCharm(ops.CharmBase, ABC): + """An abstract class for a charm that supports reconciliation.""" + + @abstractmethod + def reconcile(self, event: ops.charm.EventBase) -> None: + """Reconcile the charm state. + + Args: + event: The event that triggered the reconciliation. + """ + + class DatasourcePostgreSQL(BaseModel): """A named tuple representing a Datasource PostgreSQL. diff --git a/src/database_observer.py b/src/database_observer.py index f0a0f2c..84589c9 100644 --- a/src/database_observer.py +++ b/src/database_observer.py @@ -10,10 +10,9 @@ DatabaseEndpointsChangedEvent, DatabaseRequires, ) -from ops.charm import CharmBase from ops.framework import Object -from charm_types import DatasourcePostgreSQL +from charm_types import DatasourcePostgreSQL, ReconcilingCharm from constants import DATABASE_NAME @@ -25,7 +24,7 @@ class DatabaseObserver(Object): database: The database relation interface. """ - def __init__(self, charm: CharmBase, relation_name: str): + def __init__(self, charm: ReconcilingCharm, relation_name: str): """Initialize the oserver and register event handlers. Args: @@ -45,11 +44,11 @@ def __init__(self, charm: CharmBase, relation_name: str): def _on_database_created(self, _: DatabaseCreatedEvent) -> None: """Handle database created.""" - self._charm.reconcile() # type: ignore + self._charm.reconcile() def _on_endpoints_changed(self, _: DatabaseEndpointsChangedEvent) -> None: """Handle endpoints changed.""" - self._charm.reconcile() # type: ignore + self._charm.reconcile() def get_db(self) -> typing.Optional[DatasourcePostgreSQL]: """Return a postgresql datasource model. diff --git a/src/matrix_observer.py b/src/matrix_observer.py index fef9549..289d1d5 100644 --- a/src/matrix_observer.py +++ b/src/matrix_observer.py @@ -11,17 +11,18 @@ MatrixAuthRequirerData, MatrixAuthRequires, ) -from ops.charm import CharmBase from ops.framework import Object from pydantic import SecretStr +from charm_types import ReconcilingCharm + logger = logging.getLogger(__name__) class MatrixObserver(Object): """The Matrix relation observer.""" - def __init__(self, charm: CharmBase, relation_name: str): + def __init__(self, charm: ReconcilingCharm, relation_name: str): """Initialize the oserver and register event handlers. Args: @@ -43,7 +44,7 @@ def __init__(self, charm: CharmBase, relation_name: str): def _on_matrix_auth_request_processed(self, _: Object) -> None: """Handle the matrix auth request processed event.""" logger.info("Matrix auth request processed") - self._charm.reconcile() # type: ignore + self._charm.reconcile() def get_matrix(self) -> typing.Optional[MatrixAuthProviderData]: """Return a Matrix authentication datasource model. diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index e0f9b81..eb24dfa 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -49,6 +49,4 @@ async def app_fixture( charm = await ops_test.build_charm(".") application = await model.deploy(charm, application_name=app_name) - # await model.wait_for_idle(apps=[application.name], status="active") - yield application diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index bd23f83..5093025 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -11,6 +11,7 @@ import tempfile import ops +from juju.application import Application from pytest_operator.plugin import OpsTest @@ -144,7 +145,7 @@ async def set_config(ops_test: OpsTest, app_name: str, config: dict): async def generate_anycharm_relation( - app: ops.model.Application, + app: Application, ops_test: OpsTest, any_charm_name: str, machine: str | None, diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 1362fdf..55866b7 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -8,6 +8,7 @@ import ops import pytest +from juju.application import Application from pytest_operator.plugin import OpsTest import tests.integration.helpers @@ -17,7 +18,7 @@ @pytest.mark.asyncio @pytest.mark.abort_on_fail -async def test_lifecycle_before_relations(app: ops.model.Application, ops_test: OpsTest): +async def test_lifecycle_before_relations(app: Application, ops_test: OpsTest): """ arrange: build and deploy the charm. act: nothing. @@ -26,8 +27,7 @@ async def test_lifecycle_before_relations(app: ops.model.Application, ops_test: # Set config so the charm can start config = {"bridge_admins": "admin:example.com", "bot_nickname": "bot"} await tests.integration.helpers.set_config(ops_test, app.name, config) - # Application actually does have units - unit = app.units[0] # type: ignore + unit = app.units[0] # Mypy has difficulty with ActiveStatus assert unit.workload_status == ops.model.WaitingStatus.name # type: ignore @@ -35,7 +35,7 @@ async def test_lifecycle_before_relations(app: ops.model.Application, ops_test: @pytest.mark.asyncio @pytest.mark.abort_on_fail -async def test_lifecycle_after_relations(app: ops.model.Application, ops_test: OpsTest): +async def test_lifecycle_after_relations(app: Application, ops_test: OpsTest): """ arrange: build and deploy the charm. act: relate to a db and a matrix homeserver. @@ -43,10 +43,8 @@ async def test_lifecycle_after_relations(app: ops.model.Application, ops_test: O """ # Set config so the charm can start config = {"bridge_admins": "admin:example.com", "bot_nickname": "bot"} - await tests.integration.helpers.set_config(ops_test, app.name, config) - # await ops_test.model.wait_for_idle(apps=[app.name], status="waiting", timeout=60 * 60) - # Application actually does have units - unit = app.units[0] # type: ignore + app.set_config(config) + unit = app.units[0] # Deploy postgresql charm assert ops_test.model