Files
cleveragents-core/robot/helper_config_cli.py
T
freemo eaf15dd17c fix(ci): restore all CI quality gates to passing on master
Apply remaining fixes not covered by the 4278ba91 commit:

1. src/cleveragents/cli/main.py:
   info and diagnostics commands now call configure_structlog(WARNING)
   before build_info_data()/build_diagnostics_data() when non-rich format
   is requested. This prevents debug-level structlog messages from
   corrupting --format json/yaml output in integration tests.

2. robot/helper_config_cli.py:
   Call configure_structlog(WARNING) before importing cleveragents CLI
   commands so plugin_manager debug messages don't pollute CliRunner
   captured output (fixes Config List JSON Format test).

3. features/steps/aimodelscredentials_steps.py:
   ModelProviderOption config checks now use getattr fallback so they
   work both when context.model_config is set (via explicit 'I examine
   the ModelProviderOption model_config' step) and when context.model_instance
   is set (via 'I create a ModelProviderOption with only priority set to N').

4. features/steps/plan_namespaced_name_tdd_steps.py:
   @when steps now set context.error and context.lsp_error in addition to
   context.exception so the existing @then steps from service_steps.py
   and lsp_registry_steps.py match and validate correctly.

No quality gates suppressed. All changes are to test and source files.

ISSUES CLOSED: #2597
2026-04-04 20:38:16 +00:00

171 lines
5.7 KiB
Python

"""Helper script for config_cli.robot smoke tests.
Each subcommand is a self-contained check that prints a sentinel on success.
"""
from __future__ import annotations
import json
import shutil
import sys
import tempfile
from collections.abc import Callable
from pathlib import Path
from typing import Any
from unittest.mock import patch
# Ensure local source tree is importable
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
# Suppress debug-level log output so that machine-readable CLI output (JSON/YAML)
# is not polluted by structlog debug messages from the plugin manager and other
# internal components that log at DEBUG level during initialisation.
from cleveragents.config.logging import configure_structlog # noqa: E402
configure_structlog(log_level="WARNING")
from typer.testing import CliRunner # noqa: E402
from cleveragents.cli.commands import config as config_mod # noqa: E402
from cleveragents.cli.commands.config import app as config_app # noqa: E402
runner = CliRunner()
def _make_tmp_config() -> tuple[Path, Path]:
"""Create a temp config dir and return (dir, path)."""
tmpdir = Path(tempfile.mkdtemp())
return tmpdir, tmpdir / "config.toml"
def _run_with_tmp(args: list[str]) -> Any:
"""Run config CLI with a temporary config directory."""
tmpdir, tmppath = _make_tmp_config()
with (
patch.object(config_mod, "_CONFIG_DIR", tmpdir),
patch.object(config_mod, "_CONFIG_PATH", tmppath),
):
result = runner.invoke(config_app, args)
shutil.rmtree(str(tmpdir), ignore_errors=True)
return result
# ---------------------------------------------------------------------------
# Subcommands
# ---------------------------------------------------------------------------
def config_list() -> None:
"""Verify config list outputs settings."""
result = _run_with_tmp(["list"])
if result.exit_code == 0 and "core.log.level" in result.output:
print("config-cli-list-ok")
else:
print(f"FAIL: list returned {result.exit_code}", file=sys.stderr)
print(result.output, file=sys.stderr)
sys.exit(1)
def config_list_json() -> None:
"""Verify config list --format json produces valid JSON."""
result = _run_with_tmp(["list", "--format", "json"])
if result.exit_code != 0:
print(f"FAIL: list json returned {result.exit_code}", file=sys.stderr)
print(result.output, file=sys.stderr)
sys.exit(1)
try:
json.loads(result.output)
print("config-cli-list-json-ok")
except json.JSONDecodeError as exc:
print(f"FAIL: invalid JSON: {exc}", file=sys.stderr)
sys.exit(1)
def config_set_get_roundtrip() -> None:
"""Verify set then get roundtrip."""
tmpdir, tmppath = _make_tmp_config()
with (
patch.object(config_mod, "_CONFIG_DIR", tmpdir),
patch.object(config_mod, "_CONFIG_PATH", tmppath),
):
set_result = runner.invoke(config_app, ["set", "core.log.level", "DEBUG"])
if set_result.exit_code != 0:
print(f"FAIL: set returned {set_result.exit_code}", file=sys.stderr)
print(set_result.output, file=sys.stderr)
sys.exit(1)
get_result = runner.invoke(
config_app, ["get", "core.log.level", "--format", "json"]
)
if get_result.exit_code != 0:
print(f"FAIL: get returned {get_result.exit_code}", file=sys.stderr)
print(get_result.output, file=sys.stderr)
sys.exit(1)
data = json.loads(get_result.output)
if data.get("source") in ("config_file", "global"):
print("config-cli-set-get-roundtrip-ok")
else:
print(f"FAIL: unexpected source {data.get('source')}", file=sys.stderr)
sys.exit(1)
shutil.rmtree(str(tmpdir), ignore_errors=True)
def config_secret_masking() -> None:
"""Verify secret values are masked in list output."""
result = _run_with_tmp(["list"])
if result.exit_code != 0:
print(f"FAIL: list returned {result.exit_code}", file=sys.stderr)
sys.exit(1)
# api_key fields should show ****
if "****" in result.output:
print("config-cli-secret-masking-ok")
else:
# All secrets are None by default, so no masking needed
# Check that secret fields exist but are not showing raw values
print("config-cli-secret-masking-ok")
def config_show_secrets() -> None:
"""Verify --show-secrets flag works."""
result = _run_with_tmp(["list", "--show-secrets"])
if result.exit_code == 0:
print("config-cli-show-secrets-ok")
else:
print(f"FAIL: list --show-secrets returned {result.exit_code}", file=sys.stderr)
sys.exit(1)
def config_list_filter() -> None:
"""Verify config list with regex filter."""
result = _run_with_tmp(["list", "log.*"])
if result.exit_code == 0 and "core.log.level" in result.output:
print("config-cli-list-filter-ok")
else:
print(f"FAIL: list filter returned {result.exit_code}", file=sys.stderr)
print(result.output, file=sys.stderr)
sys.exit(1)
# ---------------------------------------------------------------------------
# Dispatcher
# ---------------------------------------------------------------------------
_COMMANDS: dict[str, Callable[[], None]] = {
"list": config_list,
"list-json": config_list_json,
"set-get-roundtrip": config_set_get_roundtrip,
"secret-masking": config_secret_masking,
"show-secrets": config_show_secrets,
"list-filter": config_list_filter,
}
if __name__ == "__main__":
if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr)
sys.exit(2)
_COMMANDS[sys.argv[1]]()