forked from HAL9000/cleveragents-core
4d31f0ed02
Add `fmt: OutputFormat` parameter to `main_callback()` in
`src/cleveragents/cli/main.py` and store the selected format in
`ctx.obj["format"]` so all subcommands can read it without needing
their own per-command `--format` flag.
Remove per-command `--format` / `fmt` parameters from `version()`,
`info()`, and `diagnostics()` commands. These commands now read the
format from `ctx.obj.get("format", OutputFormat.RICH.value)`.
The specification states: "The framework supports six distinct output
formats, selectable via the global `--format` flag." This change
aligns the implementation with the spec by making `--format` a global
option on the root `agents` command (via the Typer callback).
All six formats (json, yaml, plain, rich, table, color) are supported
via the global flag and the `-f` shorthand.
Add Behave BDD scenarios covering global `--format` flag propagation
to subcommands for all six formats. Update Robot Framework integration
tests to exercise the global `--format` flag. Update existing tests
that used per-command `--format` for version/info/diagnostics to use
the global flag instead.
ISSUES CLOSED: #2908
272 lines
10 KiB
Python
272 lines
10 KiB
Python
"""Step definitions for CLI main.py uncovered branch coverage scenarios."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import then, when
|
|
from typer.testing import CliRunner
|
|
|
|
from cleveragents.cli.main import app, show_secrets_callback
|
|
|
|
runner = CliRunner()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# show_secrets_callback
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("cli main branch show_secrets_callback is invoked with True")
|
|
def step_show_secrets_true(context: Any) -> None:
|
|
with patch("cleveragents.shared.redaction.set_show_secrets") as mock_set:
|
|
show_secrets_callback(True)
|
|
context.mock_set_show_secrets = mock_set
|
|
|
|
|
|
@then("cli main branch set_show_secrets should have been called with True")
|
|
def step_show_secrets_called(context: Any) -> None:
|
|
context.mock_set_show_secrets.assert_called_once_with(True)
|
|
|
|
|
|
@when("cli main branch show_secrets_callback is invoked with False")
|
|
def step_show_secrets_false(context: Any) -> None:
|
|
with patch("cleveragents.shared.redaction.set_show_secrets") as mock_set:
|
|
show_secrets_callback(False)
|
|
context.mock_set_show_secrets = mock_set
|
|
|
|
|
|
@then("cli main branch set_show_secrets should not have been called")
|
|
def step_show_secrets_not_called(context: Any) -> None:
|
|
context.mock_set_show_secrets.assert_not_called()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# version command - non-rich format
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('cli main branch version command is invoked with format "{fmt}"')
|
|
def step_version_non_rich(context: Any, fmt: str) -> None:
|
|
mock_data = {"version": "0.0.0-test", "python": "3.12"}
|
|
with (
|
|
patch(
|
|
"cleveragents.cli.commands.system.build_version_data",
|
|
return_value=mock_data,
|
|
),
|
|
patch("cleveragents.cli.commands.system.render_version_rich") as mock_render,
|
|
patch(
|
|
"cleveragents.cli.formatting.format_output",
|
|
return_value="formatted-version",
|
|
) as mock_fmt,
|
|
):
|
|
# --format is now a global flag on the root command, not per-command
|
|
result = runner.invoke(app, ["--format", fmt, "version"])
|
|
context.cli_result = result
|
|
context.mock_format_output = mock_fmt
|
|
context.mock_render_rich = mock_render
|
|
|
|
|
|
@then(
|
|
'cli main branch format_output should have been called for version with fmt "{fmt}"'
|
|
)
|
|
def step_version_format_output_called(context: Any, fmt: str) -> None:
|
|
context.mock_format_output.assert_called_once()
|
|
call_args = context.mock_format_output.call_args
|
|
assert call_args[0][1] == fmt, (
|
|
f"Expected format_output to be called with fmt={fmt!r}, got {call_args[0][1]!r}"
|
|
)
|
|
# render_version_rich should NOT have been called for non-rich format
|
|
context.mock_render_rich.assert_not_called()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# info command - non-rich format
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('cli main branch info command is invoked with format "{fmt}"')
|
|
def step_info_non_rich(context: Any, fmt: str) -> None:
|
|
mock_data = {"system": "test-os", "python": "3.12"}
|
|
with (
|
|
patch(
|
|
"cleveragents.cli.commands.system.build_info_data",
|
|
return_value=mock_data,
|
|
),
|
|
patch("cleveragents.cli.commands.system.render_info_rich") as mock_render,
|
|
patch(
|
|
"cleveragents.cli.formatting.format_output",
|
|
return_value="formatted-info",
|
|
) as mock_fmt,
|
|
):
|
|
# --format is now a global flag on the root command, not per-command
|
|
result = runner.invoke(app, ["--format", fmt, "info"])
|
|
context.cli_result = result
|
|
context.mock_format_output = mock_fmt
|
|
context.mock_render_rich = mock_render
|
|
|
|
|
|
@then('cli main branch format_output should have been called for info with fmt "{fmt}"')
|
|
def step_info_format_output_called(context: Any, fmt: str) -> None:
|
|
context.mock_format_output.assert_called_once()
|
|
call_args = context.mock_format_output.call_args
|
|
assert call_args[0][1] == fmt, (
|
|
f"Expected format_output to be called with fmt={fmt!r}, got {call_args[0][1]!r}"
|
|
)
|
|
context.mock_render_rich.assert_not_called()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# diagnostics command - non-rich format
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('cli main branch diagnostics command is invoked with format "{fmt}"')
|
|
def step_diagnostics_non_rich(context: Any, fmt: str) -> None:
|
|
mock_data = {"has_errors": False, "checks": []}
|
|
with (
|
|
patch(
|
|
"cleveragents.cli.commands.system.build_diagnostics_data",
|
|
return_value=mock_data,
|
|
),
|
|
patch(
|
|
"cleveragents.cli.commands.system.render_diagnostics_rich"
|
|
) as mock_render,
|
|
patch(
|
|
"cleveragents.cli.formatting.format_output",
|
|
return_value="formatted-diag",
|
|
) as mock_fmt,
|
|
):
|
|
# --format is now a global flag on the root command, not per-command
|
|
result = runner.invoke(app, ["--format", fmt, "diagnostics"])
|
|
context.cli_result = result
|
|
context.mock_format_output = mock_fmt
|
|
context.mock_render_rich = mock_render
|
|
|
|
|
|
@then(
|
|
'cli main branch format_output should have been called for diagnostics with fmt "{fmt}"'
|
|
)
|
|
def step_diagnostics_format_output_called(context: Any, fmt: str) -> None:
|
|
context.mock_format_output.assert_called_once()
|
|
call_args = context.mock_format_output.call_args
|
|
assert call_args[0][1] == fmt, (
|
|
f"Expected format_output to be called with fmt={fmt!r}, got {call_args[0][1]!r}"
|
|
)
|
|
context.mock_render_rich.assert_not_called()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# diagnostics --check with errors → exit 1
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("cli main branch diagnostics command is invoked with check and errors present")
|
|
def step_diagnostics_check_errors(context: Any) -> None:
|
|
mock_data = {"has_errors": True, "checks": [{"name": "x", "status": "FAIL"}]}
|
|
with (
|
|
patch(
|
|
"cleveragents.cli.commands.system.build_diagnostics_data",
|
|
return_value=mock_data,
|
|
),
|
|
patch("cleveragents.cli.commands.system.render_diagnostics_rich"),
|
|
):
|
|
result = runner.invoke(app, ["diagnostics", "--check"])
|
|
context.cli_result = result
|
|
|
|
|
|
@then("cli main branch diagnostics should exit with code 1")
|
|
def step_diagnostics_exit_1(context: Any) -> None:
|
|
assert context.cli_result.exit_code == 1, (
|
|
f"Expected exit code 1, got {context.cli_result.exit_code}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# diagnostics --check with no errors → exit 0
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("cli main branch diagnostics command is invoked with check but no errors")
|
|
def step_diagnostics_check_no_errors(context: Any) -> None:
|
|
mock_data = {"has_errors": False, "checks": [{"name": "y", "status": "OK"}]}
|
|
with (
|
|
patch(
|
|
"cleveragents.cli.commands.system.build_diagnostics_data",
|
|
return_value=mock_data,
|
|
),
|
|
patch("cleveragents.cli.commands.system.render_diagnostics_rich"),
|
|
):
|
|
result = runner.invoke(app, ["diagnostics", "--check"])
|
|
context.cli_result = result
|
|
|
|
|
|
@then("cli main branch diagnostics should exit with code 0")
|
|
def step_diagnostics_exit_0(context: Any) -> None:
|
|
assert context.cli_result.exit_code == 0, (
|
|
f"Expected exit code 0, got {context.cli_result.exit_code}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# init - generic Exception handler (L376-379)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("cli main branch init command raises a generic Exception")
|
|
def step_init_generic_exception(context: Any) -> None:
|
|
with patch(
|
|
"cleveragents.cli.commands.project.init_command",
|
|
side_effect=RuntimeError("something broke"),
|
|
):
|
|
result = runner.invoke(app, ["init", "testproject"])
|
|
context.cli_result = result
|
|
|
|
|
|
@then('cli main branch init output should contain "Unexpected error"')
|
|
def step_init_unexpected_error(context: Any) -> None:
|
|
assert context.cli_result.exit_code != 0, (
|
|
f"Expected non-zero exit code, got {context.cli_result.exit_code}"
|
|
)
|
|
# The error message is printed to err_console (stderr), which CliRunner
|
|
# may capture depending on mix_stderr. Check both output and the result.
|
|
# At minimum, the exit code must be non-zero (the handler raises Exit(1)).
|
|
# We also verify the handler was reached by checking exit_code == 1.
|
|
assert context.cli_result.exit_code == 1, (
|
|
f"Expected exit code 1 from the generic exception handler, "
|
|
f"got {context.cli_result.exit_code}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# __name__ == "__main__" block (L626-627)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("cli main branch the module is executed as __main__")
|
|
def step_main_block(context: Any) -> None:
|
|
mock_main = MagicMock(return_value=0)
|
|
mock_exit = MagicMock()
|
|
with (
|
|
patch("cleveragents.cli.main.main", mock_main),
|
|
patch("cleveragents.cli.main.sys") as mock_sys,
|
|
):
|
|
mock_sys.exit = mock_exit
|
|
mock_sys.argv = ["cleveragents"]
|
|
# Simulate the if __name__ == "__main__" block directly
|
|
# since runpy can have import side effects.
|
|
# The block is: sys.exit(main())
|
|
|
|
# Execute the guarded block manually
|
|
mock_sys.exit(mock_main())
|
|
|
|
context.mock_main = mock_main
|
|
context.mock_sys_exit = mock_exit
|
|
|
|
|
|
@then("cli main branch sys.exit should have been called with main result")
|
|
def step_main_sys_exit(context: Any) -> None:
|
|
context.mock_main.assert_called_once()
|
|
context.mock_sys_exit.assert_called_once_with(0)
|