Files
placeholder/features/steps/cli_global_format_flag_steps.py
freemo 4d31f0ed02 fix(cli): promote --format to global CLI callback option per spec
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
2026-04-05 08:39:14 +00:00

159 lines
5.4 KiB
Python

"""Step definitions for global --format flag propagation tests.
Tests that the --format flag is a global option on the root `agents` command
(via the Typer callback), not a per-command option, per the specification.
"""
from __future__ import annotations
import json
from typing import Any
from unittest.mock import patch
import yaml
from behave import given, then, when
from typer.testing import CliRunner
from cleveragents.cli.main import app
@given("the global format flag test runner is set up")
def step_setup_runner(context: Any) -> None:
"""Set up the test runner for global format flag tests."""
context.runner = CliRunner()
context.cli_result = None
context.ctx_obj = {}
def _invoke_with_mocked_system(runner: CliRunner, args: list[str]) -> Any:
"""Invoke the CLI app with mocked system commands."""
with (
patch(
"cleveragents.cli.commands.system.build_version_data",
return_value={
"version": "3.7.0",
"python": "3.12.0",
"platform": "linux",
},
),
patch(
"cleveragents.cli.commands.system.render_version_rich",
),
patch(
"cleveragents.cli.commands.system.build_info_data",
return_value={
"version": "3.7.0",
"config_path": "/tmp/config.toml",
"db_url": "sqlite:///test.db",
},
),
patch(
"cleveragents.cli.commands.system.render_info_rich",
),
patch(
"cleveragents.cli.commands.system.build_diagnostics_data",
return_value={
"checks": [],
"has_errors": False,
},
),
patch(
"cleveragents.cli.commands.system.render_diagnostics_rich",
),
):
return runner.invoke(app, args)
@when('I invoke the CLI with global args "{args_str}"')
def step_invoke_cli_global_args(context: Any, args_str: str) -> None:
"""Invoke the CLI with the given argument string."""
args = args_str.split()
result = _invoke_with_mocked_system(context.runner, args)
context.cli_result = result
context.cli_output = result.output
@then("the global format command should succeed")
def step_global_format_command_succeeds(context: Any) -> None:
"""Verify the command exited with code 0."""
result = context.cli_result
assert result.exit_code == 0, (
f"Expected exit code 0, got {result.exit_code}.\n"
f"Output: {result.output}\n"
f"Exception: {result.exception}"
)
@then("the global format output should be valid JSON")
def step_global_format_output_is_valid_json(context: Any) -> None:
"""Verify the output is parseable JSON."""
output = context.cli_output.strip()
try:
parsed = json.loads(output)
context.parsed_json = parsed
except json.JSONDecodeError as exc:
raise AssertionError(
f"Output is not valid JSON: {exc}\nOutput was: {output!r}"
) from exc
@then("the global format output should be valid YAML")
def step_global_format_output_is_valid_yaml(context: Any) -> None:
"""Verify the output is parseable YAML."""
output = context.cli_output.strip()
try:
parsed = yaml.safe_load(output)
context.parsed_yaml = parsed
except yaml.YAMLError as exc:
raise AssertionError(
f"Output is not valid YAML: {exc}\nOutput was: {output!r}"
) from exc
@then("the global format output should contain plain text")
def step_global_format_output_contains_plain_text(context: Any) -> None:
"""Verify the output contains plain key: value text."""
output = context.cli_output.strip()
assert output, f"Expected non-empty plain text output, got: {output!r}"
@then('the global format JSON output should contain key "{key}"')
def step_global_format_json_output_contains_key(context: Any, key: str) -> None:
"""Verify the parsed JSON output contains the given key."""
parsed = getattr(context, "parsed_json", None)
if parsed is None:
output = context.cli_output.strip()
parsed = json.loads(output)
assert key in parsed, (
f"Expected key '{key}' in JSON output. Keys found: {list(parsed.keys())}"
)
@when('I call main_callback directly with format "{fmt}"')
def step_call_main_callback_directly(context: Any, fmt: str) -> None:
"""Call main_callback via the CLI runner to test ctx.obj storage."""
result = _invoke_with_mocked_system(context.runner, ["--format", fmt, "version"])
context.cli_result = result
context.captured_fmt = fmt
@then('the context object should have format "{expected_fmt}"')
def step_ctx_obj_has_format(context: Any, expected_fmt: str) -> None:
"""Verify the format was stored correctly.
Since we can't easily inspect ctx.obj after the fact without modifying
the command, we verify indirectly: the command succeeded and the format
was accepted (no 'Invalid value' error for the format option).
"""
result = context.cli_result
# The command should succeed (format was accepted)
assert result.exit_code == 0, (
f"Expected exit code 0 for format '{expected_fmt}', "
f"got {result.exit_code}.\nOutput: {result.output}"
)
# Verify no "Invalid value" error for the format option
assert "Invalid value" not in result.output, (
f"Got 'Invalid value' error for format '{expected_fmt}': {result.output}"
)
context.verified_fmt = expected_fmt