02c75fb4bb
Add structured data builders and Rich renderers for version, info, and diagnostics commands. Support --format rich/plain/json/yaml output parity and --check flag for diagnostics (exits non-zero on errors). Includes Behave scenarios, Robot smoke tests, ASV benchmarks, and updates existing tests to match the new Rich panel output format.
238 lines
8.9 KiB
Python
238 lines
8.9 KiB
Python
"""Step definitions for core system commands (version, info, diagnostics).
|
|
|
|
Covers features/cli_core.feature — the enhanced CLI0.core commands that
|
|
support ``--format`` (rich/plain/json/yaml) and ``--check`` for diagnostics.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from behave import then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.cli.commands.system import (
|
|
build_diagnostics_data,
|
|
build_info_data,
|
|
build_version_data,
|
|
)
|
|
from cleveragents.cli.formatting import format_output
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _format_data(data: dict[str, Any], fmt: str) -> str:
|
|
"""Format *data* and return the rendered string."""
|
|
if fmt == "rich":
|
|
from io import StringIO
|
|
from unittest.mock import patch
|
|
|
|
from rich.console import Console
|
|
|
|
buf = StringIO()
|
|
console = Console(file=buf, width=200, no_color=True)
|
|
|
|
with patch("cleveragents.cli.main.get_console", return_value=console):
|
|
if data.get("checks") is not None:
|
|
from cleveragents.cli.commands.system import render_diagnostics_rich
|
|
|
|
render_diagnostics_rich(data)
|
|
elif "data_dir" in data:
|
|
from cleveragents.cli.commands.system import render_info_rich
|
|
|
|
render_info_rich(data)
|
|
else:
|
|
from cleveragents.cli.commands.system import render_version_rich
|
|
|
|
render_version_rich(data)
|
|
|
|
return buf.getvalue()
|
|
return format_output(data, fmt)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# VERSION — When steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I run the system version command")
|
|
def step_run_system_version(context: Context) -> None:
|
|
"""Run version with default (rich) format."""
|
|
data = build_version_data()
|
|
context.sys_version_data = data
|
|
context.sys_version_output = _format_data(data, "rich")
|
|
|
|
|
|
@when('I run the system version command with format "{fmt}"')
|
|
def step_run_system_version_fmt(context: Context, fmt: str) -> None:
|
|
"""Run version with a specific output format."""
|
|
data = build_version_data()
|
|
context.sys_version_data = data
|
|
context.sys_version_output = _format_data(data, fmt)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# VERSION — Then steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the system version output should contain "{text}"')
|
|
def step_system_version_output_contains(context: Context, text: str) -> None:
|
|
output = context.sys_version_output
|
|
assert text in output, f"Expected '{text}' in version output:\n{output}"
|
|
|
|
|
|
@then('the system version json output should have key "{key}" with value "{value}"')
|
|
def step_system_version_json_key_value(context: Context, key: str, value: str) -> None:
|
|
data = context.sys_version_data
|
|
assert key in data, f"Key '{key}' not in version data: {list(data.keys())}"
|
|
assert str(data[key]) == value, f"Expected {key}={value!r}, got {data[key]!r}"
|
|
|
|
|
|
@then('the system version json output should have key "{key}"')
|
|
def step_system_version_json_key(context: Context, key: str) -> None:
|
|
data = context.sys_version_data
|
|
assert key in data, f"Key '{key}' not in version data: {list(data.keys())}"
|
|
|
|
|
|
@then('the system version json output should have nested key "{key}"')
|
|
def step_system_version_json_nested_key(context: Context, key: str) -> None:
|
|
data = context.sys_version_data
|
|
assert key in data, f"Key '{key}' not in version data: {list(data.keys())}"
|
|
assert isinstance(data[key], dict), (
|
|
f"Expected '{key}' to be a dict, got {type(data[key])}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# INFO — When steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I run the system info command")
|
|
def step_run_system_info(context: Context) -> None:
|
|
"""Run info with default (rich) format."""
|
|
data = build_info_data()
|
|
context.sys_info_data = data
|
|
context.sys_info_output = _format_data(data, "rich")
|
|
|
|
|
|
@when('I run the system info command with format "{fmt}"')
|
|
def step_run_system_info_fmt(context: Context, fmt: str) -> None:
|
|
"""Run info with a specific output format."""
|
|
data = build_info_data()
|
|
context.sys_info_data = data
|
|
context.sys_info_output = _format_data(data, fmt)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# INFO — Then steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the system info output should contain "{text}"')
|
|
def step_system_info_output_contains(context: Context, text: str) -> None:
|
|
output = context.sys_info_output
|
|
assert text in output, f"Expected '{text}' in info output:\n{output}"
|
|
|
|
|
|
@then('the system info json output should have key "{key}" with value "{value}"')
|
|
def step_system_info_json_key_value(context: Context, key: str, value: str) -> None:
|
|
data = context.sys_info_data
|
|
assert key in data, f"Key '{key}' not in info data: {list(data.keys())}"
|
|
assert str(data[key]) == value, f"Expected {key}={value!r}, got {data[key]!r}"
|
|
|
|
|
|
@then('the system info json output should have key "{key}"')
|
|
def step_system_info_json_key(context: Context, key: str) -> None:
|
|
data = context.sys_info_data
|
|
assert key in data, f"Key '{key}' not in info data: {list(data.keys())}"
|
|
|
|
|
|
@then('the system info json output should have nested key "{key}"')
|
|
def step_system_info_json_nested_key(context: Context, key: str) -> None:
|
|
data = context.sys_info_data
|
|
assert key in data, f"Key '{key}' not in info data: {list(data.keys())}"
|
|
assert isinstance(data[key], dict), (
|
|
f"Expected '{key}' to be a dict, got {type(data[key])}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# DIAGNOSTICS — When steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I run the system diagnostics command")
|
|
def step_run_system_diagnostics(context: Context) -> None:
|
|
"""Run diagnostics with default (rich) format."""
|
|
data = build_diagnostics_data()
|
|
context.sys_diag_data = data
|
|
context.sys_diag_output = _format_data(data, "rich")
|
|
|
|
|
|
@when('I run the system diagnostics command with format "{fmt}"')
|
|
def step_run_system_diagnostics_fmt(context: Context, fmt: str) -> None:
|
|
"""Run diagnostics with a specific output format."""
|
|
data = build_diagnostics_data()
|
|
context.sys_diag_data = data
|
|
context.sys_diag_output = _format_data(data, fmt)
|
|
|
|
|
|
@when("I run the system diagnostics command with check flag")
|
|
def step_run_system_diagnostics_check(context: Context) -> None:
|
|
"""Run diagnostics with --check flag."""
|
|
data = build_diagnostics_data()
|
|
context.sys_diag_data = data
|
|
context.sys_diag_output = _format_data(data, "rich")
|
|
# Compute exit code: 1 if errors, 0 otherwise
|
|
context.sys_diag_exit_code = 1 if data["has_errors"] else 0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# DIAGNOSTICS — Then steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the system diagnostics output should contain "{text}"')
|
|
def step_system_diag_output_contains(context: Context, text: str) -> None:
|
|
output = context.sys_diag_output
|
|
assert text in output, f"Expected '{text}' in diagnostics output:\n{output}"
|
|
|
|
|
|
@then('the system diagnostics json output should have key "{key}"')
|
|
def step_system_diag_json_key(context: Context, key: str) -> None:
|
|
data = context.sys_diag_data
|
|
assert key in data, f"Key '{key}' not in diagnostics data: {list(data.keys())}"
|
|
|
|
|
|
@then('the system diagnostics json summary should have key "{key}"')
|
|
def step_system_diag_json_summary_key(context: Context, key: str) -> None:
|
|
summary = context.sys_diag_data.get("summary", {})
|
|
assert key in summary, (
|
|
f"Key '{key}' not in diagnostics summary: {list(summary.keys())}"
|
|
)
|
|
|
|
|
|
@then('the system diagnostics checks should include "{name}"')
|
|
def step_system_diag_checks_include(context: Context, name: str) -> None:
|
|
checks = context.sys_diag_data.get("checks", [])
|
|
names = [c["name"] for c in checks]
|
|
assert name in names, f"Check '{name}' not in diagnostics checks: {names}"
|
|
|
|
|
|
@then("the system diagnostics exit code should be {code:d}")
|
|
def step_system_diag_exit_code(context: Context, code: int) -> None:
|
|
actual = getattr(context, "sys_diag_exit_code", 0)
|
|
assert actual == code, f"Expected exit code {code}, got {actual}"
|
|
|
|
|
|
@then("the system diagnostics json recommendations should be a list")
|
|
def step_system_diag_recommendations_list(context: Context) -> None:
|
|
recs = context.sys_diag_data.get("recommendations")
|
|
assert isinstance(recs, list), (
|
|
f"Expected recommendations to be a list, got {type(recs)}"
|
|
)
|