Add unit tests for workflow commands#2480
Open
berendt wants to merge 1 commit into
Open
Conversation
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="tests/unit/commands/test_apply.py" line_range="475-314" />
<code_context>
+ handle_task.assert_not_called()
+
+
+def test_handle_loadbalancer_task_wait_uses_parent_rc(mocker, loguru_logs):
+ handle_task = mocker.patch("osism.tasks.handle_task", return_value=2)
+ cmd = make_command(apply.Run)
+ t = MagicMock()
+ t.children = [MagicMock(task_id="child-1"), MagicMock(task_id="child-2")]
+
+ rc = cmd.handle_loadbalancer_task(t, True, "log", 300)
+
+ assert rc == 2
+ handle_task.assert_called_once_with(t.parent, True, "log", 300)
+ t.parent.get.assert_not_called()
+ t.get.assert_called_once_with()
+ messages = [r["message"] for r in loguru_logs]
+ assert any(
+ "Task child-1 (loadbalancer) is running in background" in m for m in messages
+ )
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for loadbalancer GroupResult failure scenarios and rc propagation
Current tests only cover successful `handle_task` returns and don’t assert behavior when children in the `GroupResult` fail or raise. Please add cases where a child errors and check the resulting rc and logging, so it’s clear whether failures in chained loadbalancer plays affect the user-visible result or are ignored.
Suggested implementation:
```python
def test_handle_loadbalancer_task_wait_uses_parent_rc(mocker, loguru_logs):
handle_task = mocker.patch("osism.tasks.handle_task", return_value=2)
cmd = make_command(apply.Run)
t = MagicMock()
t.children = [MagicMock(task_id="child-1"), MagicMock(task_id="child-2")]
rc = cmd.handle_loadbalancer_task(t, True, "log", 300)
assert rc == 2
handle_task.assert_called_once_with(t.parent, True, "log", 300)
t.parent.get.assert_not_called()
t.get.assert_called_once_with()
messages = [r["message"] for r in loguru_logs]
assert any(
"Task child-1 (loadbalancer) is running in background" in m for m in messages
)
def test_handle_loadbalancer_task_child_error_does_not_change_rc(mocker, loguru_logs):
handle_task = mocker.patch("osism.tasks.handle_task", return_value=2)
cmd = make_command(apply.Run)
t = MagicMock()
child_1 = MagicMock(task_id="child-1")
child_2 = MagicMock(task_id="child-2")
t.children = [child_1, child_2]
# Simulate failure when retrieving the child result
child_1.get.side_effect = Exception("boom")
child_2.get.return_value = None
rc = cmd.handle_loadbalancer_task(t, True, "log", 300)
# The parent rc is still the user-visible result
assert rc == 2
handle_task.assert_called_once_with(t.parent, True, "log", 300)
# Parent task is not re-fetched, only children are consulted
t.parent.get.assert_not_called()
t.get.assert_called_once_with()
child_1.get.assert_called_once_with()
child_2.get.assert_called_once_with()
messages = [r["message"] for r in loguru_logs]
# Child failure is logged so it is visible to the user
assert any(
"Task child-1 (loadbalancer) failed" in m for m in messages
)
def test_handle_loadbalancer_task_child_nonzero_rc_is_logged_but_ignored(
mocker, loguru_logs
):
handle_task = mocker.patch("osism.tasks.handle_task", return_value=2)
cmd = make_command(apply.Run)
t = MagicMock()
child_1 = MagicMock(task_id="child-1")
child_2 = MagicMock(task_id="child-2")
t.children = [child_1, child_2]
# Simulate a child play finishing with a non-zero rc
child_1.get.return_value = 99
child_2.get.return_value = 0
rc = cmd.handle_loadbalancer_task(t, True, "log", 300)
# The parent rc remains the user-visible result even if a child fails
assert rc == 2
handle_task.assert_called_once_with(t.parent, True, "log", 300)
t.parent.get.assert_not_called()
t.get.assert_called_once_with()
child_1.get.assert_called_once_with()
child_2.get.assert_called_once_with()
messages = [r["message"] for r in loguru_logs]
# Make sure the failure is clearly logged with the child rc
assert any(
"Task child-1 (loadbalancer) finished with rc=99" in m for m in messages
)
```
To make these tests pass, `handle_loadbalancer_task` should:
1. Iterate over `t.children` and call `child.get()` for each child in the `GroupResult`.
2. Catch exceptions raised by `child.get()` and log a message containing `"Task {child.task_id} (loadbalancer) failed"`.
3. When `child.get()` returns a non-zero rc, log a message containing `"Task {child.task_id} (loadbalancer) finished with rc={rc}"`.
4. Always return the rc from `handle_task(t.parent, ...)` (the parent result) as the effective rc so the tests asserting rc==2 succeed.
If the current implementation behaves differently, it should be adjusted to match these semantics or the expected log messages can be adapted to the actual wording used in the logger.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
berendt
force-pushed
the
unit-tests-workflow-commands
branch
from
July 17, 2026 11:27
1a3b259 to
bacf8ee
Compare
Cover the workflow-oriented CLI command modules under osism/commands/ with unit tests following the established pattern (command instantiated with mocked app/options, args built via get_parser().parse_args()): - apply: _prepare_task environment resolution (ceph-/kolla- prefix stripping, sub-environments, kubernetes routing, the loadbalancer-ng chain-into-group case, kolla_action/kolla_action_ng arguments, the osism-ansible runtime override and the custom fallback), collection expansion into celery groups/chains with dry-run and show-tree modes, GroupResult dispatch in handle_role, task-lock enforcement, the Ansible-facts freshness backoff, // role splitting and retries - check: the pure helpers get_file_info, collect_file_info, parse_stat_output and _compare_file_info; container id and mount source detection from cgroup/mountinfo; Mount.take_action guard rails and rc contract; Inode.take_action explicit and sampled file lists - compose: SSH command construction including the argument join and the known-hosts fallback warning - sync: Facts/CephKeys/Sonic task dispatch with lock ordering, the release base.yml version lookup, SBOM image derivation, skopeo-based SBOM extraction from an OCI layout and the versions.yml rendering/writing paths including --dry-run - get (extended): Hostvars/Hosts happy paths, VersionsManager container labels, Tasks inspect rendering, Facts cache lookups and truncation, States rendering - log: ara parameter forwarding, docker logs over SSH, /var/log path confinement and shell quoting in File, clush/ssh host routing and rc passthrough, the Opensearch query loop - console: the module-level host resolution helpers (DNS, Netbox fallback, inventory groups, interactive selection) and take_action routing for the container/ansible-console/clush/ssh host syntaxes - validate (extended): validator-to-runtime routing with environment overrides and playbook rewriting, the _handle_task wait/no-wait contract, and the SCS compliance check command construction and cleanup - wait (extended): get_all_task_ids merging, the requeue loop for PENDING/STARTED tasks, unavailable-task handling, --refresh and the script output format All Celery task calls, subprocesses, Docker/Netbox/Redis clients and interactive prompts are mocked; log assertions use a loguru sink since caplog does not capture loguru records. The tests pin current behavior, including a few candidates for follow-up fixes: handle_role logs task.task_id before the GroupResult check (GroupResult has no such attribute), the collection branch of apply.take_action returns None, --refresh in wait is ignored when task ids are passed on the CLI, and log.File error messages use printf-style placeholders that loguru does not interpolate. Closes #2363 Assisted-by: Claude:claude-fable-5 Signed-off-by: Christian Berendt <berendt@osism.tech>
berendt
force-pushed
the
unit-tests-workflow-commands
branch
from
July 17, 2026 11:35
bacf8ee to
45bd287
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #2363: unit tests for the workflow-oriented CLI command modules under
osism/commands/(apply,check,compose,sync,get,log,console,validate,wait).What's covered
test_apply.py(33 tests),test_check.py(44),test_compose.py(3),test_sync.py(27),test_log.py(16),test_console.py(27)test_validate.py(+13),test_wait.py(+9),test_get.py(+14)All test targets from the issue are implemented, including the pure helpers in
check.pyand the module-level host-resolution helpers inconsole.pythat the issue flags for ≥95 % coverage. All Celery task calls, subprocesses, Docker/Netbox/Redis clients and interactive prompts are mocked; log assertions use a loguru sink (loguru_logsfixture) sincecaplogdoes not capture loguru records.Current behavior pinned — follow-up fix candidates
The tests characterize the code as it is today; a few of the pinned behaviors look like latent bugs worth separate issues:
apply.Run.handle_rolelogstask.task_idbefore theGroupResultisinstance check, butGroupResulthas notask_idattribute — the realloadbalancer-ngpath would raiseAttributeError(apply.py:403).apply.Run.take_actionbreaks the retry loop withrc=Noneand returnsNoneinstead of an exit code (anticipated by the issue).wait --refreshis a no-op when task ids are passed on the CLI:do_refreshis only enabled when the id list comes from the inspector.log.Fileerror logs use printf-style%splaceholders, which loguru does not interpolate — the placeholders appear literally in the log output.validate.Run._handle_taskmixest.id(fetch) andt.task_id(log/print), and its timeout message says "(sync inventory)".flake8andblack --checkpass locally; the test suite itself runs in CI per project workflow.Closes #2363
🤖 Generated with Claude Code