d98666651f
CI / lint (pull_request) Successful in 15s
CI / typecheck (pull_request) Successful in 27s
CI / security (pull_request) Successful in 22s
CI / quality (pull_request) Successful in 15s
CI / integration_tests (pull_request) Successful in 4m24s
CI / build (pull_request) Successful in 16s
CI / unit_tests (pull_request) Successful in 9m27s
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / security (push) Waiting to run
CI / quality (push) Waiting to run
CI / unit_tests (push) Waiting to run
CI / integration_tests (push) Waiting to run
CI / coverage (push) Blocked by required conditions
CI / build (push) Waiting to run
CI / docker (push) Blocked by required conditions
CI / coverage (pull_request) Successful in 6m55s
CI / docker (pull_request) Successful in 39s
- Enforce config-only 'action create --config <file>' (remove legacy inline flags: --name, --strategy-actor, --execution-actor, --definition-of-done, --arg) - Load config via ActionConfigSchema.from_yaml_file() then Action.from_config() with clear validation error surfacing - Remove 'action available' subcommand (no draft state; actions are available by default) - Add REGEX positional arg to 'action list' for name filtering - Add Short Name and Definition of Done summary to all CLI outputs - Add 'action list --namespace/-n' and '--state/-s' filters - Update existing tests to match new config-only create flow - Add features/action_cli_spec_alignment.feature (19 scenarios) - Add robot/action_cli_spec.robot smoke tests - Add benchmarks/action_cli_bench.py ASV benchmarks - Add docs/reference/action_cli.md
168 lines
5.1 KiB
Python
168 lines
5.1 KiB
Python
"""Helper script for action_cli_spec.robot smoke tests.
|
|
|
|
Each subcommand is a self-contained check that prints a sentinel on success.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, 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)
|
|
|
|
from typer.testing import CliRunner # noqa: E402
|
|
|
|
from cleveragents.cli.commands.action import app as action_app # noqa: E402
|
|
from cleveragents.domain.models.core.action import Action, ActionState # noqa: E402
|
|
from cleveragents.domain.models.core.plan import NamespacedName # noqa: E402
|
|
|
|
runner = CliRunner()
|
|
|
|
_VALID_YAML = """\
|
|
name: local/smoke-action
|
|
description: Smoke test action
|
|
strategy_actor: openai/gpt-4
|
|
execution_actor: openai/gpt-4
|
|
definition_of_done: All smoke tests pass
|
|
"""
|
|
|
|
|
|
def _mock_action(name: str = "local/smoke-action") -> Action:
|
|
return Action(
|
|
namespaced_name=NamespacedName.parse(name),
|
|
description="Smoke test action",
|
|
definition_of_done="All smoke tests pass",
|
|
strategy_actor="openai/gpt-4",
|
|
execution_actor="openai/gpt-4",
|
|
state=ActionState.AVAILABLE,
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
|
|
|
|
def _write_yaml(content: str) -> str:
|
|
fd, path = tempfile.mkstemp(suffix=".yaml")
|
|
with os.fdopen(fd, "w") as fh:
|
|
fh.write(content)
|
|
return path
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Subcommands
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def create_requires_config() -> None:
|
|
"""Verify that create without --config fails."""
|
|
result = runner.invoke(action_app, ["create"])
|
|
if result.exit_code != 0:
|
|
print("action-cli-create-requires-config-ok")
|
|
else:
|
|
print("FAIL: create without --config should fail", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def create_rejects_name() -> None:
|
|
"""Verify that --name flag is rejected."""
|
|
result = runner.invoke(action_app, ["create", "--name", "local/test"])
|
|
if result.exit_code != 0:
|
|
print("action-cli-legacy-flag-rejected")
|
|
else:
|
|
print("FAIL: --name should be rejected", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def available_removed() -> None:
|
|
"""Verify that available subcommand no longer exists."""
|
|
result = runner.invoke(action_app, ["available", "local/test"])
|
|
if result.exit_code != 0:
|
|
print("action-cli-available-removed-ok")
|
|
else:
|
|
print("FAIL: available should not exist", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def list_filters() -> None:
|
|
"""Verify that list accepts --namespace and --state."""
|
|
mock_service = MagicMock()
|
|
mock_service.list_actions.return_value = [_mock_action()]
|
|
|
|
with patch(
|
|
"cleveragents.cli.commands.action._get_lifecycle_service",
|
|
return_value=mock_service,
|
|
):
|
|
r1 = runner.invoke(action_app, ["list", "--namespace", "local"])
|
|
r2 = runner.invoke(action_app, ["list", "--state", "available"])
|
|
if r1.exit_code == 0 and r2.exit_code == 0:
|
|
print("action-cli-list-filters-ok")
|
|
else:
|
|
print("FAIL: list filters", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def show_name() -> None:
|
|
"""Verify show accepts a namespaced name."""
|
|
mock_service = MagicMock()
|
|
mock_service.get_action_by_name.return_value = _mock_action()
|
|
|
|
with patch(
|
|
"cleveragents.cli.commands.action._get_lifecycle_service",
|
|
return_value=mock_service,
|
|
):
|
|
result = runner.invoke(action_app, ["show", "local/smoke-action"])
|
|
if result.exit_code == 0:
|
|
print("action-cli-show-name-ok")
|
|
else:
|
|
print(f"FAIL: show returned {result.exit_code}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def archive_name() -> None:
|
|
"""Verify archive accepts a namespaced name."""
|
|
mock_service = MagicMock()
|
|
mock_service.get_action_by_name.return_value = _mock_action()
|
|
mock_service.archive_action.return_value = _mock_action()
|
|
|
|
with patch(
|
|
"cleveragents.cli.commands.action._get_lifecycle_service",
|
|
return_value=mock_service,
|
|
):
|
|
result = runner.invoke(action_app, ["archive", "local/smoke-action"])
|
|
if result.exit_code == 0:
|
|
print("action-cli-archive-name-ok")
|
|
else:
|
|
print(f"FAIL: archive returned {result.exit_code}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main dispatcher
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_COMMANDS = {
|
|
"create-requires-config": create_requires_config,
|
|
"create-rejects-name": create_rejects_name,
|
|
"available-removed": available_removed,
|
|
"list-filters": list_filters,
|
|
"show-name": show_name,
|
|
"archive-name": archive_name,
|
|
}
|
|
|
|
|
|
def main() -> None:
|
|
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]]()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|