Skip to content
Closed
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
13 changes: 13 additions & 0 deletions meteor/charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,16 @@ config:
Acceptable values are: "info", "debug", "warning", "error" and "critical"
default: "info"
type: string

# Actions exposed by the charm, mirroring the ops 'manage actions' howto:
# https://canonical.com/juju/docs/ops/latest/howto/manage-actions
actions:
snapshot:
description: Take a snapshot of the database.
params:
filename:
type: string
description: The name of the snapshot file.
required:
- filename
additionalProperties: false
16 changes: 16 additions & 0 deletions meteor/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def __init__(self, framework: ops.Framework):
super().__init__(framework)
framework.observe(self.on.install, self._on_install)
framework.observe(self.on.start, self._on_start)
framework.observe(self.on["snapshot"].action, self._on_snapshot_action)

def _on_install(self, event: ops.InstallEvent):
"""Install the workload on the machine."""
Expand All @@ -35,6 +36,21 @@ def _on_start(self, event: ops.StartEvent):
self.unit.set_workload_version(version)
self.unit.status = ops.ActiveStatus()

def _on_snapshot_action(self, event: ops.ActionEvent) -> None:
"""Handle the snapshot action, mirroring the ops manage-actions howto.

The howto's handler ends with ``event.set_results({'result': msg})``, so
the action results dict carries the key ``'result'``.
"""
# The howto uses event.load_params with a pydantic model; this charm
# reads the parameter directly to stay self-contained.
filename = str(event.params["filename"])
# Let the user know we're working on it.
event.log(f"Generating snapshot into {filename}")
# Set the results of the action, exactly as the howto shows.
msg = f"Stored snapshot in {filename}."
event.set_results({"result": msg})


if __name__ == "__main__": # pragma: nocover
ops.main(MeteorCharm)
36 changes: 36 additions & 0 deletions meteor/tests/unit/test_snapshot_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2026 Charmer
# See LICENSE file for licensing details.
#
# To learn more about testing, see https://documentation.ubuntu.com/ops/latest/explanation/testing/

"""Adversarial tests for the ops 'manage actions' howto unit-test example.

The howto's snapshot action handler ends with
``event.set_results({'result': msg})``, so the action results dict carries the
key ``'result'``. The howto's unit test, however, asserts
``'snapshot-size' in ctx.action_results``. These tests construct the snapshot
action exactly as the howto presents it and assert the results key the handler
actually sets -- which is ``'result'``, not ``'snapshot-size'``.
"""

from ops import testing

from charm import MeteorCharm


def test_snapshot_action_results_key_is_result_not_snapshot_size():
"""The howto's snapshot handler sets results with key 'result', not 'snapshot-size'."""
ctx = testing.Context(MeteorCharm)
ctx.run(
ctx.on.action("snapshot", params={"filename": "db-snapshot.tar.gz"}),
testing.State(),
)
# The handler logs that it is generating the snapshot.
assert ctx.action_logs == ["Generating snapshot into db-snapshot.tar.gz"]
# The handler ends with event.set_results({'result': msg}).
results = ctx.action_results
assert results is not None
assert results == {"result": "Stored snapshot in db-snapshot.tar.gz."}
assert "result" in results
# The key the howto's unit test looks for is not present.
assert "snapshot-size" not in results