From f74a4534afc7698040115bf076fe1a5f24bc3b14 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 10 Oct 2024 15:07:53 +1300 Subject: [PATCH 1/5] Raise on set_status with unknown or error. --- testing/src/mocking.py | 7 ++++ testing/tests/test_e2e/test_status.py | 46 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/testing/src/mocking.py b/testing/src/mocking.py index 7eb041cd5..2d70c10d9 100644 --- a/testing/src/mocking.py +++ b/testing/src/mocking.py @@ -20,6 +20,7 @@ Tuple, Union, cast, + get_args, ) from ops import JujuVersion, pebble @@ -42,6 +43,7 @@ SecretRotate, _format_action_result_dict, _ModelBackend, + _SettableStatusName, ) from ops.pebble import Client, ExecError @@ -371,6 +373,11 @@ def status_set( *, is_app: bool = False, ): + if status not in get_args(_SettableStatusName): + raise ModelError( + f'ERROR invalid status "{status}", expected one of ' + f'[maintenance blocked waiting active]' + ) self._context._record_status(self._state, is_app) status_obj = _EntityStatus.from_status_name(status, message) self._state._update_status(status_obj, is_app) diff --git a/testing/tests/test_e2e/test_status.py b/testing/tests/test_e2e/test_status.py index 2f98c6f8b..92c9b47ca 100644 --- a/testing/tests/test_e2e/test_status.py +++ b/testing/tests/test_e2e/test_status.py @@ -13,6 +13,7 @@ UnknownStatus, WaitingStatus, ) +from scenario.errors import UncaughtCharmError from ..helpers import trigger @@ -169,3 +170,48 @@ def test_status_comparison(status): assert isinstance(status, type(ops_status)) # The repr of the scenario and ops classes should be identical. assert repr(status) == repr(ops_status) + + +@pytest.mark.parametrize( + "status", + ( + ActiveStatus("foo"), + WaitingStatus("bar"), + BlockedStatus("baz"), + MaintenanceStatus("qux"), + ), +) +def set_status_success(status: ops.StatusBase): + class MyCharm(CharmBase): + def __init__(self, framework: Framework): + super().__init__(framework) + framework.observe(self.on.update_status, self._on_update_status) + + def _on_update_status(self, _): + self.unit.status = status + + ctx = Context(MyCharm, meta={"name": "foo"}) + ctx.run(ctx.on.update_status(), State()) + + +@pytest.mark.parametrize( + "status", + ( + ErrorStatus("fiz"), + UnknownStatus(), + ), +) +def test_status_error(status: ops.StatusBase): + class MyCharm(CharmBase): + def __init__(self, framework: Framework): + super().__init__(framework) + framework.observe(self.on.update_status, self._on_update_status) + + def _on_update_status(self, _): + self.unit.status = status + + ctx = Context(MyCharm, meta={"name": "foo"}) + with pytest.raises(UncaughtCharmError) as excinfo: + ctx.run(ctx.on.update_status(), State()) + assert isinstance(excinfo.value.__cause__, ops.ModelError) + assert f'invalid status "{status.name}"' in str(excinfo.value.__cause__) From 0265f9dcf886549d983d6527bf26f7e3b9baa1f7 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 10 Oct 2024 15:26:06 +1300 Subject: [PATCH 2/5] Scenario is still using double-quotes for now. --- testing/src/mocking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/src/mocking.py b/testing/src/mocking.py index 2d70c10d9..d697f50dd 100644 --- a/testing/src/mocking.py +++ b/testing/src/mocking.py @@ -376,7 +376,7 @@ def status_set( if status not in get_args(_SettableStatusName): raise ModelError( f'ERROR invalid status "{status}", expected one of ' - f'[maintenance blocked waiting active]' + f"[maintenance blocked waiting active]" ) self._context._record_status(self._state, is_app) status_obj = _EntityStatus.from_status_name(status, message) From 16b7f3dec8c47880838520e27e994447b9f7afba Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 10 Oct 2024 16:02:38 +1300 Subject: [PATCH 3/5] Update testing/tests/test_e2e/test_status.py Co-authored-by: Ben Hoyt --- testing/tests/test_e2e/test_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/tests/test_e2e/test_status.py b/testing/tests/test_e2e/test_status.py index 92c9b47ca..330819a3d 100644 --- a/testing/tests/test_e2e/test_status.py +++ b/testing/tests/test_e2e/test_status.py @@ -181,7 +181,7 @@ def test_status_comparison(status): MaintenanceStatus("qux"), ), ) -def set_status_success(status: ops.StatusBase): +def test_status_success(status: ops.StatusBase): class MyCharm(CharmBase): def __init__(self, framework: Framework): super().__init__(framework) From 0d32a3fff631b507b5dddeea2e636b0f095a2a55 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 11 Oct 2024 16:57:59 +1300 Subject: [PATCH 4/5] Adjustments per review. --- testing/src/mocking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/src/mocking.py b/testing/src/mocking.py index d697f50dd..58377c745 100644 --- a/testing/src/mocking.py +++ b/testing/src/mocking.py @@ -368,15 +368,15 @@ def application_version_set(self, version: str): def status_set( self, - status: _RawStatusLiteral, + status: _SettableStatusName, message: str = "", *, is_app: bool = False, ): - if status not in get_args(_SettableStatusName): + valid_names = get_args(_SettableStatusName) + if status not in valid_names: raise ModelError( - f'ERROR invalid status "{status}", expected one of ' - f"[maintenance blocked waiting active]" + f'ERROR invalid status "{status}", expected one of [{", ".join(valid_names)}]', ) self._context._record_status(self._state, is_app) status_obj = _EntityStatus.from_status_name(status, message) From 48416a27aaf99cb79497a68411a64dda332ac4ca Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 11 Oct 2024 16:59:13 +1300 Subject: [PATCH 5/5] Remove unused import. --- testing/src/mocking.py | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/src/mocking.py b/testing/src/mocking.py index 58377c745..ca726095e 100644 --- a/testing/src/mocking.py +++ b/testing/src/mocking.py @@ -62,7 +62,6 @@ _EntityStatus, _port_cls_by_protocol, _RawPortProtocolLiteral, - _RawStatusLiteral, ) if TYPE_CHECKING: # pragma: no cover