fix(v3.7.0): resolve issue #1422 #1495
@@ -0,0 +1,134 @@
|
||||
"""Step definitions for validation add output format feature (issue #1422).
|
||||
|
||||
Tests that ``agents validation add`` rich output includes:
|
||||
- Config: field (path to config file)
|
||||
- Created: field (timestamp)
|
||||
- Capability panel (Read-Only, Checkpointable, Timeout)
|
||||
- OK Validation registered footer message
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from behave import given, then, when
|
||||
from behave.runner import Context
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from cleveragents.cli.commands.validation import app as validation_app
|
||||
|
||||
_VALIDATION_YAML = """\
|
||||
name: local/run-tests
|
||||
description: Run unit tests with coverage
|
||||
source: custom
|
||||
mode: required
|
||||
code: |
|
||||
def run(inputs):
|
||||
return {"passed": True}
|
||||
"""
|
||||
|
||||
_runner = CliRunner()
|
||||
|
||||
|
||||
def _make_mock_validation(
|
||||
name: str = "local/run-tests",
|
||||
) -> dict[str, Any]:
|
||||
"""Create a mock validation dict as returned by the service layer."""
|
||||
return {
|
||||
"name": name,
|
||||
"description": "Run unit tests with coverage",
|
||||
"source": "custom",
|
||||
"tool_type": "validation",
|
||||
"mode": "required",
|
||||
"timeout": 300,
|
||||
}
|
||||
|
||||
|
||||
def _patch_val_svc(context: Context) -> Any:
|
||||
return patch(
|
||||
"cleveragents.cli.commands.validation._get_tool_registry_service",
|
||||
return_value=context.val_output_mock_service,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Background
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@given("a validation add output format test runner with mocks")
|
||||
def step_val_output_format_runner(context: Context) -> None:
|
||||
context.val_output_runner = _runner
|
||||
context.val_output_result = None
|
||||
context.val_output_yaml_path = None
|
||||
context.val_output_mock_service = MagicMock()
|
||||
context.val_output_mock_service.register_tool.return_value = _make_mock_validation()
|
||||
context.val_output_mock_service.update_tool.return_value = _make_mock_validation()
|
||||
context.val_output_mock_service.get_tool.return_value = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Given steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@given("a validation add output format YAML config at a known path")
|
||||
def step_val_output_yaml_config(context: Context) -> None:
|
||||
fd, path = tempfile.mkstemp(suffix=".yaml")
|
||||
with os.fdopen(fd, "w") as fh:
|
||||
fh.write(_VALIDATION_YAML)
|
||||
context.val_output_yaml_path = path
|
||||
|
||||
|
||||
@given("the validation add output format validation already exists")
|
||||
def step_val_output_already_exists(context: Context) -> None:
|
||||
context.val_output_mock_service.get_tool.return_value = _make_mock_validation()
|
||||
context.val_output_mock_service.update_tool.return_value = _make_mock_validation()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# When steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@when("I invoke validation add output format with rich output")
|
||||
def step_val_output_invoke_rich(context: Context) -> None:
|
||||
with _patch_val_svc(context):
|
||||
context.val_output_result = _runner.invoke(
|
||||
validation_app,
|
||||
["add", "--config", context.val_output_yaml_path],
|
||||
)
|
||||
|
||||
|
||||
@when("I invoke validation add output format with --update flag")
|
||||
def step_val_output_invoke_update(context: Context) -> None:
|
||||
with _patch_val_svc(context):
|
||||
context.val_output_result = _runner.invoke(
|
||||
validation_app,
|
||||
["add", "--config", context.val_output_yaml_path, "--update"],
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Then assertions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@then("the validation add output format result should succeed")
|
||||
def step_val_output_result_success(context: Context) -> None:
|
||||
assert context.val_output_result is not None
|
||||
assert context.val_output_result.exit_code == 0, (
|
||||
f"Expected exit 0, got {context.val_output_result.exit_code}. "
|
||||
f"Output: {context.val_output_result.output}"
|
||||
)
|
||||
|
||||
|
||||
@then('the validation add output format output should contain "{text}"')
|
||||
def step_val_output_contains(context: Context, text: str) -> None:
|
||||
assert context.val_output_result is not None
|
||||
assert text in context.val_output_result.output, (
|
||||
f"Expected '{text}' in output. Got: {context.val_output_result.output}"
|
||||
)
|
||||
@@ -0,0 +1,66 @@
|
||||
@tdd_issue @tdd_issue_1422
|
||||
|
|
||||
Feature: Bug #1422 — validation add output missing Capability panel, Config path, and Created timestamp
|
||||
As a user of the CleverAgents CLI
|
||||
I want the ``agents validation add`` command to display the Config path,
|
||||
Created timestamp, Capability panel, and footer message in its rich output
|
||||
So that the output matches the specification at docs/specification.md lines 9319-9360
|
||||
|
||||
Background:
|
||||
Given a validation add output format test runner with mocks
|
||||
|
||||
Scenario: validation add rich output includes Config path
|
||||
Given a validation add output format YAML config at a known path
|
||||
When I invoke validation add output format with rich output
|
||||
Then the validation add output format result should succeed
|
||||
And the validation add output format output should contain "Config:"
|
||||
|
||||
Scenario: validation add rich output includes Created timestamp
|
||||
Given a validation add output format YAML config at a known path
|
||||
When I invoke validation add output format with rich output
|
||||
Then the validation add output format result should succeed
|
||||
And the validation add output format output should contain "Created:"
|
||||
|
||||
Scenario: validation add rich output includes Capability panel
|
||||
Given a validation add output format YAML config at a known path
|
||||
When I invoke validation add output format with rich output
|
||||
Then the validation add output format result should succeed
|
||||
And the validation add output format output should contain "Capability"
|
||||
|
||||
Scenario: validation add rich output includes Read-Only enforced field
|
||||
Given a validation add output format YAML config at a known path
|
||||
When I invoke validation add output format with rich output
|
||||
Then the validation add output format result should succeed
|
||||
And the validation add output format output should contain "Read-Only:"
|
||||
|
||||
Scenario: validation add rich output includes Checkpointable enforced field
|
||||
Given a validation add output format YAML config at a known path
|
||||
When I invoke validation add output format with rich output
|
||||
Then the validation add output format result should succeed
|
||||
And the validation add output format output should contain "Checkpointable:"
|
||||
|
||||
Scenario: validation add rich output includes Timeout field
|
||||
Given a validation add output format YAML config at a known path
|
||||
When I invoke validation add output format with rich output
|
||||
Then the validation add output format result should succeed
|
||||
And the validation add output format output should contain "Timeout:"
|
||||
|
||||
Scenario: validation add rich output includes OK footer message
|
||||
Given a validation add output format YAML config at a known path
|
||||
When I invoke validation add output format with rich output
|
||||
Then the validation add output format result should succeed
|
||||
And the validation add output format output should contain "OK Validation registered"
|
||||
|
||||
Scenario: validation add rich output includes Validation Registered panel title
|
||||
Given a validation add output format YAML config at a known path
|
||||
When I invoke validation add output format with rich output
|
||||
Then the validation add output format result should succeed
|
||||
And the validation add output format output should contain "Validation Registered"
|
||||
|
||||
Scenario: validation add with --update flag also shows Config and Capability panel
|
||||
Given a validation add output format YAML config at a known path
|
||||
And the validation add output format validation already exists
|
||||
When I invoke validation add output format with --update flag
|
||||
Then the validation add output format result should succeed
|
||||
And the validation add output format output should contain "Config:"
|
||||
And the validation add output format output should contain "Capability"
|
||||
And the validation add output format output should contain "OK Validation registered"
|
||||
@@ -48,6 +48,8 @@ Based on implementation_plan.md -- Task C1.tool.cli.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any
|
||||
|
||||
@@ -68,7 +70,9 @@ from cleveragents.domain.models.core.tool import Validation, ValidationMode
|
||||
|
||||
# Create sub-app for validation commands
|
||||
app = typer.Typer(help="Manage validations (pass/fail tools) and resource attachments.")
|
||||
console = Console()
|
||||
# Use stdout so Typer/CliRunner captures output in result.output (BDD tests
|
||||
# assert on result.output; Rich defaults to stderr which would break those).
|
||||
console = Console(file=sys.stdout)
|
||||
|
||||
# Reusable --format option description
|
||||
_FORMAT_HELP = "Output format: json, yaml, plain, table, or rich (default: rich)"
|
||||
@@ -136,8 +140,18 @@ def _print_validation(
|
||||
tool: Any,
|
||||
title: str = "Validation",
|
||||
fmt: str = OutputFormat.RICH.value,
|
||||
config_path: str | None = None,
|
||||
created_at: datetime | None = None,
|
||||
) -> None:
|
||||
"""Print validation details in the requested format."""
|
||||
"""Print validation details in the requested format.
|
||||
|
||||
Args:
|
||||
tool: The validation tool object to print
|
||||
title: Panel title (e.g., "Validation Registered")
|
||||
fmt: Output format (rich, json, yaml, etc.)
|
||||
config_path: Path to the config file (optional)
|
||||
created_at: Timestamp of creation (optional)
|
||||
"""
|
||||
data = _validation_spec_dict(tool)
|
||||
if fmt != OutputFormat.RICH.value:
|
||||
console.print(format_output(data, fmt))
|
||||
@@ -155,6 +169,15 @@ def _print_validation(
|
||||
f"[bold]Mode:[/bold] {mode}"
|
||||
)
|
||||
|
||||
# Add Config field if provided
|
||||
if config_path:
|
||||
details += f"\n[bold]Config:[/bold] {config_path}"
|
||||
|
||||
# Add Created field if provided
|
||||
if created_at:
|
||||
created_str = created_at.strftime("%Y-%m-%d %H:%M")
|
||||
details += f"\n[bold]Created:[/bold] {created_str}"
|
||||
|
||||
wraps = data.get("wraps")
|
||||
if wraps:
|
||||
details += f"\n[bold]Wraps:[/bold] {wraps}"
|
||||
@@ -162,8 +185,21 @@ def _print_validation(
|
||||
if transform:
|
||||
details += f"\n[bold]Transform:[/bold] {transform}"
|
||||
|
||||
# Print main panel
|
||||
console.print(Panel(details, title=title, expand=False))
|
||||
|
||||
# Add Capability panel
|
||||
timeout = getattr(tool, "timeout", 300)
|
||||
capability_details = (
|
||||
f"[bold]Read-Only:[/bold] true (enforced)\n"
|
||||
f"[bold]Checkpointable:[/bold] false (enforced)\n"
|
||||
f"[bold]Timeout:[/bold] {timeout}s"
|
||||
)
|
||||
console.print(Panel(capability_details, title="Capability", expand=False))
|
||||
|
||||
# Add footer message
|
||||
console.print("[green]✓ OK Validation registered[/green]")
|
||||
|
||||
|
||||
@app.command("add")
|
||||
def add(
|
||||
@@ -239,11 +275,23 @@ def add(
|
||||
existing = service.get_tool(validation.name)
|
||||
if existing is not None:
|
||||
registered = service.update_tool(validation)
|
||||
_print_validation(registered, title="Validation Updated", fmt=fmt)
|
||||
_print_validation(
|
||||
registered,
|
||||
title="Validation Updated",
|
||||
fmt=fmt,
|
||||
config_path=str(config),
|
||||
created_at=datetime.now(),
|
||||
)
|
||||
return
|
||||
|
||||
registered = service.register_tool(validation)
|
||||
_print_validation(registered, title="Validation Registered", fmt=fmt)
|
||||
_print_validation(
|
||||
registered,
|
||||
title="Validation Registered",
|
||||
fmt=fmt,
|
||||
config_path=str(config),
|
||||
created_at=datetime.now(),
|
||||
)
|
||||
|
||||
except FileNotFoundError as exc:
|
||||
console.print(f"[red]Config file error:[/red] {exc}")
|
||||
|
||||
Reference in New Issue
Block a user
[BLOCKING] No Robot Framework integration test has been added in
robot/to cover the newagents validation addoutput format. Per the multi-level testing mandate in CONTRIBUTING.md, all new CLI behavior must be covered at both the unit (Behave) and integration (Robot Framework) levels.Issue #1422 subtasks explicitly list:
Tests (Robot): Update integration test output assertions.Please add a
.robotfile inrobot/that invokesagents validation addand asserts the presence of the Capability panel, Config path, Created timestamp, and OK footer message in the command output.Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker