fix: Update for Click 8.2+ compatibility and fix quality gates #3774
@@ -3,6 +3,15 @@ Feature: Config CLI safety-net coverage
|
||||
I want thorough safety-net tests for every function in config.py
|
||||
So that 100% line and branch coverage is maintained as the code evolves
|
||||
|
||||
# =====================================================================
|
||||
# _get_config_dir (L53-58) - CLEVERAGENTS_HOME env var path
|
||||
# =====================================================================
|
||||
|
||||
Scenario: safety-net _get_config_dir returns CLEVERAGENTS_HOME path when set
|
||||
Given the safety-net env var "CLEVERAGENTS_HOME" is set to "/tmp/sn-cleveragents-home-xyz"
|
||||
When the safety-net config dir resolver runs
|
||||
Then the safety-net config dir result should be "/tmp/sn-cleveragents-home-xyz"
|
||||
|
||||
# =====================================================================
|
||||
# _normalize_key (L91-96)
|
||||
# =====================================================================
|
||||
|
||||
@@ -86,6 +86,11 @@ Feature: Plan CLI spec alignment
|
||||
When I run plan list with action "local/test-action"
|
||||
Then the plan spec list should succeed
|
||||
|
||||
Scenario: Plan list with --namespace filter
|
||||
Given plan spec alignment plans exist
|
||||
When I run plan list with namespace "myteam"
|
||||
Then the plan spec list should succeed
|
||||
|
||||
Scenario: Plan list with regex filter
|
||||
Given plan spec alignment plans exist
|
||||
When I run plan list with regex "test-action"
|
||||
|
||||
@@ -34,6 +34,7 @@ from cleveragents.cli.commands._config_helpers import (
|
||||
_validate_key,
|
||||
)
|
||||
from cleveragents.cli.commands.config import (
|
||||
_get_config_dir,
|
||||
_read_config_file,
|
||||
_resolution_chain,
|
||||
_resolve_source,
|
||||
@@ -201,6 +202,23 @@ def step_sn_mock_modified_field(context: Context) -> None:
|
||||
context._cleanup_handlers.append(context._sn_resolve_patch3.stop)
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# _get_config_dir (L53-58) - CLEVERAGENTS_HOME env var path
|
||||
# ===================================================================
|
||||
|
||||
|
||||
@when("the safety-net config dir resolver runs")
|
||||
def step_sn_get_config_dir(context: Context) -> None:
|
||||
context._sn_config_dir = _get_config_dir()
|
||||
|
||||
|
||||
@then('the safety-net config dir result should be "{expected}"')
|
||||
def step_sn_config_dir_equals(context: Context, expected: str) -> None:
|
||||
assert str(context._sn_config_dir) == expected, (
|
||||
f"Expected '{expected}', got '{context._sn_config_dir}'"
|
||||
)
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# _normalize_key (L91-96)
|
||||
# ===================================================================
|
||||
|
||||
@@ -312,6 +312,12 @@ def step_plan_list_action(context: Context, action: str) -> None:
|
||||
context.result = context.runner.invoke(plan_app, ["list", "--action", action])
|
||||
|
||||
|
||||
@when('I run plan list with namespace "{namespace}"')
|
||||
def step_plan_list_namespace(context: Context, namespace: str) -> None:
|
||||
"""Run list with --namespace filter."""
|
||||
context.result = context.runner.invoke(plan_app, ["list", "--namespace", namespace])
|
||||
|
||||
|
||||
@when('I run plan list with regex "{regex}"')
|
||||
def step_plan_list_regex(context: Context, regex: str) -> None:
|
||||
"""Run list with a regex positional argument."""
|
||||
|
||||
@@ -22,7 +22,7 @@ Show Automation Profile
|
||||
|
||||
Show Automation Profile JSON
|
||||
[Documentation] Show a profile in JSON format
|
||||
[Tags] tdd_issue tdd_issue_4204 tdd_expected_fail
|
||||
[Tags] tdd_issue tdd_issue_4204
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} show-json cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} show-json-ok
|
||||
@@ -41,7 +41,7 @@ List Automation Profiles
|
||||
|
||||
List Automation Profiles JSON
|
||||
[Documentation] List profiles in JSON format
|
||||
[Tags] tdd_issue tdd_issue_4204 tdd_expected_fail
|
||||
[Tags] tdd_issue tdd_issue_4204
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} list-json cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} list-json-ok
|
||||
@@ -60,7 +60,7 @@ Remove Built-in Profile Fails
|
||||
|
||||
All Automation Profile CLI Tests
|
||||
[Documentation] Run all automation-profile CLI tests
|
||||
[Tags] tdd_issue tdd_issue_4204 tdd_expected_fail
|
||||
[Tags] tdd_issue tdd_issue_4204
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} all cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} all-tests-ok
|
||||
|
||||
@@ -26,7 +26,7 @@ Config List JSON Format
|
||||
|
||||
Config Set Get Roundtrip
|
||||
[Documentation] Verify that ``config set`` followed by ``config get`` returns the value
|
||||
[Tags] tdd_issue tdd_issue_4302 tdd_expected_fail
|
||||
[Tags] tdd_issue tdd_issue_4302
|
||||
|
||||
${result}= Run Process ${PYTHON} ${HELPER} set-get-roundtrip cwd=${WORKSPACE}
|
||||
Log ${result.stdout}
|
||||
|
||||
@@ -149,7 +149,9 @@ def test_show_json() -> None:
|
||||
assert result.exit_code == 0, f"show json failed: {result.output}"
|
||||
parsed = _extract_json(result.output)
|
||||
assert isinstance(parsed, dict)
|
||||
assert parsed["name"] == "manual"
|
||||
# JSON output is wrapped in a CLI response envelope: {"data": {...}, ...}
|
||||
data = parsed.get("data", parsed)
|
||||
assert data["name"] == "manual"
|
||||
print("show-json-ok")
|
||||
|
||||
|
||||
@@ -186,9 +188,11 @@ def test_list_json() -> None:
|
||||
f"Expected dict at top level, got {type(parsed).__name__}. "
|
||||
f"Output: {result.output[:300]}"
|
||||
)
|
||||
# JSON output is wrapped in a CLI response envelope: {"data": {...}, ...}
|
||||
inner = parsed.get("data", parsed)
|
||||
# Validate profiles wrapper
|
||||
assert "profiles" in parsed, f"Missing 'profiles' key. Keys: {list(parsed.keys())}"
|
||||
profiles = parsed["profiles"]
|
||||
assert "profiles" in inner, f"Missing 'profiles' key. Keys: {list(inner.keys())}"
|
||||
profiles = inner["profiles"]
|
||||
assert isinstance(profiles, list), (
|
||||
f"'profiles' must be a list, got {type(profiles)}"
|
||||
)
|
||||
@@ -202,8 +206,8 @@ def test_list_json() -> None:
|
||||
"Full profile dict leaked into list output (found 'phase_transitions')"
|
||||
)
|
||||
# Validate summary
|
||||
assert "summary" in parsed, f"Missing 'summary' key. Keys: {list(parsed.keys())}"
|
||||
summary = parsed["summary"]
|
||||
assert "summary" in inner, f"Missing 'summary' key. Keys: {list(inner.keys())}"
|
||||
summary = inner["summary"]
|
||||
assert "built_in" in summary, f"Missing 'built_in' in summary: {summary}"
|
||||
assert "total" in summary, f"Missing 'total' in summary: {summary}"
|
||||
assert summary["built_in"] >= 8, (
|
||||
|
||||
@@ -109,7 +109,7 @@ def config_set_get_roundtrip() -> None:
|
||||
data = (
|
||||
envelope.get("data", envelope) if isinstance(envelope, dict) else envelope
|
||||
)
|
||||
if data.get("source") in ("config_file", "global"):
|
||||
if data.get("source") in ("config", "config_file", "global"):
|
||||
print("config-cli-set-get-roundtrip-ok")
|
||||
else:
|
||||
print(f"FAIL: unexpected source {data.get('source')}", file=sys.stderr)
|
||||
|
||||
@@ -12,6 +12,7 @@ from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from datetime import UTC, datetime
|
||||
@@ -48,7 +49,16 @@ from cleveragents.cli.renderers import _get_console
|
||||
app = typer.Typer(help="Manage configuration settings for CleverAgents.")
|
||||
console = _get_console()
|
||||
|
||||
_CONFIG_DIR = Path.home() / ".cleveragents"
|
||||
|
||||
def _get_config_dir() -> Path:
|
||||
"""Return the config directory, respecting CLEVERAGENTS_HOME if set."""
|
||||
home_env = os.environ.get("CLEVERAGENTS_HOME", "").strip()
|
||||
if home_env:
|
||||
return Path(home_env)
|
||||
return Path.home() / ".cleveragents"
|
||||
|
||||
|
||||
_CONFIG_DIR = _get_config_dir()
|
||||
_CONFIG_PATH = _CONFIG_DIR / "config.toml"
|
||||
_FORMAT_HELP = "Output format: json, yaml, plain, table, or rich (default: rich)"
|
||||
|
||||
|
||||
@@ -2945,6 +2945,14 @@ def lifecycle_list_plans(
|
||||
help="Filter by action name",
|
||||
),
|
||||
] = None,
|
||||
namespace: Annotated[
|
||||
str | None,
|
||||
typer.Option(
|
||||
"--namespace",
|
||||
"-n",
|
||||
help="Filter by namespace (e.g. 'local', 'myteam')",
|
||||
),
|
||||
] = None,
|
||||
fmt: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
@@ -2964,6 +2972,7 @@ def lifecycle_list_plans(
|
||||
agents plan list --phase strategize
|
||||
agents plan list --state processing
|
||||
agents plan list "^myteam/"
|
||||
agents plan list --namespace myteam
|
||||
"""
|
||||
try:
|
||||
import re
|
||||
@@ -3000,7 +3009,9 @@ def lifecycle_list_plans(
|
||||
)
|
||||
raise typer.Abort() from exc
|
||||
|
||||
plans = service.list_plans(phase=phase_filter, project_name=project_id)
|
||||
plans = service.list_plans(
|
||||
namespace=namespace, phase=phase_filter, project_name=project_id
|
||||
)
|
||||
|
||||
# Apply processing state filter
|
||||
if state_filter:
|
||||
@@ -3116,8 +3127,13 @@ def lifecycle_list_plans(
|
||||
else:
|
||||
active_filters.append("[yellow]Action:[/yellow] (any)")
|
||||
|
||||
if namespace:
|
||||
active_filters.append(f"[yellow]Namespace:[/yellow] {namespace}")
|
||||
else:
|
||||
active_filters.append("[yellow]Namespace:[/yellow] (any)")
|
||||
|
||||
# Only show Filters panel if at least one filter is active
|
||||
if phase_filter or state_filter or project_id or action_filter:
|
||||
if phase_filter or state_filter or project_id or action_filter or namespace:
|
||||
filters_text = "\n".join(active_filters)
|
||||
filters_panel = Panel(filters_text, title="Filters", border_style="dim")
|
||||
console.print(filters_panel)
|
||||
|
||||
Reference in New Issue
Block a user