Files
cleveragents-core/features/steps/actor_cli_steps.py
T

1093 lines
39 KiB
Python

"""Step definitions for the Actor CLI feature."""
from __future__ import annotations
import json
import tempfile
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch
from behave import given, then, when
from typer.testing import CliRunner
from cleveragents.cli.commands.actor import app as actor_app
from cleveragents.core.exceptions import (
BusinessRuleViolation,
NotFoundError,
ValidationError,
)
from cleveragents.domain.models.core.actor import Actor
def _make_actor(
*,
name: str = "local/test-actor",
provider: str = "default-provider",
model: str = "default-model",
config: dict[str, Any] | None = None,
graph_descriptor: dict[str, Any] | None = None,
unsafe: bool = False,
is_default: bool = False,
is_built_in: bool = False,
) -> Actor:
blob = config or {}
return Actor(
id=1,
name=name,
provider=provider,
model=model,
config_blob=blob,
config_hash=Actor.compute_hash(blob),
graph_descriptor=graph_descriptor,
unsafe=unsafe,
is_built_in=is_built_in,
is_default=is_default,
)
def _register_cleanup(context, path: Path) -> None:
context._cleanup_handlers.append(lambda: path.unlink(missing_ok=True))
@given("an actor CLI runner")
def step_impl(context):
context.runner = CliRunner()
@given("I have an actor JSON config file")
def step_impl(context):
context.actor_config_data = {
"provider": "openai",
"model": "gpt-4o-mini",
"temperature": 0.5,
"max_tokens": 256,
}
handle = tempfile.NamedTemporaryFile(
delete=False, suffix=".json", mode="w", encoding="utf-8"
)
json.dump(context.actor_config_data, handle)
handle.flush()
handle.close()
context.actor_config_path = Path(handle.name)
_register_cleanup(context, context.actor_config_path)
@given("I have an actor JSON config file with options")
def step_impl(context):
context.actor_config_data = {
"provider": "openai",
"model": "gpt-4o",
"options": {"temperature": 0.4, "enabled": True},
}
handle = tempfile.NamedTemporaryFile(
delete=False, suffix=".json", mode="w", encoding="utf-8"
)
json.dump(context.actor_config_data, handle)
handle.flush()
handle.close()
context.actor_config_path = Path(handle.name)
_register_cleanup(context, context.actor_config_path)
@given("I have an actor list config file")
def step_impl(context):
context.actor_config_data = [1, 2, 3]
handle = tempfile.NamedTemporaryFile(
delete=False, suffix=".json", mode="w", encoding="utf-8"
)
json.dump(context.actor_config_data, handle)
handle.flush()
handle.close()
context.actor_config_path = Path(handle.name)
_register_cleanup(context, context.actor_config_path)
@given("I have an actor YAML config file returning empty data")
def step_impl(context):
context.actor_config_data = {
"provider": "existing-provider",
"model": "existing-model",
}
handle = tempfile.NamedTemporaryFile(delete=False, suffix=".yaml")
handle.write(b"provider: existing-provider\nmodel: existing-model\n")
handle.flush()
handle.close()
context.actor_config_path = Path(handle.name)
_register_cleanup(context, context.actor_config_path)
@given("I have an actor YAML-only config file")
def step_impl(context):
context.actor_config_data = {
"provider": "openai",
"model": "gpt-4o-mini",
"yaml_only": True,
}
handle = tempfile.NamedTemporaryFile(
delete=False, suffix=".yaml", mode="w", encoding="utf-8"
)
handle.write("provider: openai\nmodel: gpt-4o-mini\nyaml_only: true\n")
handle.flush()
handle.close()
context.actor_config_path = Path(handle.name)
_register_cleanup(context, context.actor_config_path)
@given("I have an actor graph config file")
def step_impl(context):
context.actor_config_data = {
"provider": "graph-provider",
"model": "graph-model",
"graph": {"nodes": ["start"], "edges": []},
}
handle = tempfile.NamedTemporaryFile(
delete=False, suffix=".json", mode="w", encoding="utf-8"
)
json.dump(context.actor_config_data, handle)
handle.flush()
handle.close()
context.actor_config_path = Path(handle.name)
_register_cleanup(context, context.actor_config_path)
@given("I have an empty actor YAML config file")
def step_impl(context):
context.actor_config_data = {}
context.expected_error = "provider is required"
handle = tempfile.NamedTemporaryFile(
delete=False, suffix=".yaml", mode="w", encoding="utf-8"
)
handle.write("")
handle.flush()
handle.close()
context.actor_config_path = Path(handle.name)
_register_cleanup(context, context.actor_config_path)
@given("I have an unsafe actor JSON config file")
def step_impl(context):
context.actor_config_data = {
"provider": "openai",
"model": "gpt-4o-mini",
"unsafe": True,
}
handle = tempfile.NamedTemporaryFile(
delete=False, suffix=".json", mode="w", encoding="utf-8"
)
json.dump(context.actor_config_data, handle)
handle.flush()
handle.close()
context.actor_config_path = Path(handle.name)
_register_cleanup(context, context.actor_config_path)
@when("I run actor add without config")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_registry = MagicMock()
mock_container.return_value.actor_service.return_value = MagicMock()
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(actor_app, ["add", "test-actor"])
context.mock_actor_registry = mock_actor_registry
context.expected_error = "Config file is required"
@when("I run actor add with that config")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_registry = MagicMock()
if isinstance(context.actor_config_data, dict):
mock_actor = _make_actor(config=context.actor_config_data)
mock_actor_registry.upsert_actor.return_value = mock_actor
else:
mock_actor_registry.upsert_actor.side_effect = ValidationError(
"Config must be a JSON/YAML object"
)
mock_container.return_value.actor_service.return_value = MagicMock()
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(
actor_app,
[
"add",
"test-actor",
"--config",
str(context.actor_config_path),
],
)
context.mock_actor_registry = mock_actor_registry
context.expected_config_blob = (
dict(context.actor_config_data)
if isinstance(context.actor_config_data, dict)
else None
)
if isinstance(context.expected_config_blob, dict):
context.expected_config_blob.setdefault("unsafe", False)
context.expected_allow_unsafe = False
if not isinstance(context.actor_config_data, dict):
context.expected_error = "Config must be a JSON/YAML object"
elif context.actor_config_data == {}:
context.expected_error = "provider is required"
elif isinstance(
context.actor_config_data, dict
) and context.actor_config_data.get("unsafe"):
context.expected_error = "Actor config is marked unsafe"
else:
context.expected_error = None
@when("I run actor add with that config and options overrides")
def step_impl(context):
overrides = {"temperature": 0.9, "max_tokens": 128, "mode": "fast"}
option_args: list[str] = []
for key, value in overrides.items():
formatted = json.dumps(value) if not isinstance(value, str) else value
option_args.extend(["--option", f"{key}={formatted}"])
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_registry = MagicMock()
mock_actor = _make_actor(config=context.actor_config_data)
mock_actor_registry.upsert_actor.return_value = mock_actor
mock_container.return_value.actor_service.return_value = MagicMock()
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(
actor_app,
[
"add",
"test-actor",
"--config",
str(context.actor_config_path),
*option_args,
],
)
context.mock_actor_registry = mock_actor_registry
context.expected_config_blob = dict(context.actor_config_data)
context.expected_config_blob.setdefault("unsafe", False)
merged_options = dict(context.expected_config_blob.get("options", {}))
merged_options.update(overrides)
context.expected_config_blob["options"] = merged_options
context.expected_allow_unsafe = False
context.expected_error = None
@when("I run actor add with boolean option override")
def step_impl(context):
option_args = ["--option", "enabled=false"]
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_registry = MagicMock()
mock_actor = _make_actor(config=context.actor_config_data)
mock_actor_registry.upsert_actor.return_value = mock_actor
mock_container.return_value.actor_service.return_value = MagicMock()
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(
actor_app,
[
"add",
"test-actor",
"--config",
str(context.actor_config_path),
*option_args,
],
)
context.mock_actor_registry = mock_actor_registry
context.expected_config_blob = dict(context.actor_config_data)
context.expected_config_blob.setdefault("unsafe", False)
merged_options = dict(context.actor_config_data.get("options", {}))
merged_options["enabled"] = False
context.expected_config_blob["options"] = merged_options
context.expected_allow_unsafe = False
context.expected_error = None
@when("I run actor add with malformed option")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_container.return_value.actor_service.return_value = MagicMock()
mock_container.return_value.actor_registry.return_value = MagicMock()
context.result = context.runner.invoke(
actor_app,
[
"add",
"test-actor",
"--config",
str(context.actor_config_path),
"--option",
"invalid",
],
)
context.expected_error = "Options must be provided as key=value pairs"
@when("I run actor add with empty option key")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_container.return_value.actor_service.return_value = MagicMock()
mock_container.return_value.actor_registry.return_value = MagicMock()
context.result = context.runner.invoke(
actor_app,
[
"add",
"test-actor",
"--config",
str(context.actor_config_path),
"--option",
"=true",
],
)
context.expected_error = "Option keys cannot be empty"
@when("I run actor add with that config and unsafe flag")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_registry = MagicMock()
if isinstance(context.actor_config_data, dict):
mock_actor = _make_actor(config=context.actor_config_data, unsafe=True)
mock_actor_registry.upsert_actor.return_value = mock_actor
else:
mock_actor_registry.upsert_actor.side_effect = ValidationError(
"Config must be a JSON/YAML object"
)
mock_container.return_value.actor_service.return_value = MagicMock()
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(
actor_app,
[
"add",
"test-actor",
"--config",
str(context.actor_config_path),
"--unsafe",
],
)
context.mock_actor_registry = mock_actor_registry
context.expected_config_blob = (
dict(context.actor_config_data)
if isinstance(context.actor_config_data, dict)
else None
)
if isinstance(context.expected_config_blob, dict):
context.expected_config_blob.setdefault("unsafe", True)
context.expected_allow_unsafe = True
if not isinstance(context.actor_config_data, dict):
context.expected_error = "Config must be a JSON/YAML object"
elif context.actor_config_data == {}:
context.expected_error = "provider is required"
else:
context.expected_error = None
@when("I run actor add via service with that config")
def step_impl(context):
class ServiceOnlyContainer:
def __init__(self, actor_service: MagicMock):
self._actor_service = actor_service
def actor_service(self):
return self._actor_service
actor_service = MagicMock()
graph_descriptor = context.actor_config_data.get("graph")
actor_service.upsert_actor.return_value = _make_actor(
name="service/graph-actor",
provider=context.actor_config_data.get("provider", "openai"),
model=context.actor_config_data.get("model", "gpt-4o"),
config=context.actor_config_data,
graph_descriptor=graph_descriptor,
)
with patch("cleveragents.application.container.get_container") as mock_container:
mock_container.return_value = ServiceOnlyContainer(actor_service)
context.result = context.runner.invoke(
actor_app,
[
"add",
"service/graph-actor",
"--config",
str(context.actor_config_path),
],
)
context.actor_service = actor_service
context.graph_descriptor = graph_descriptor
@when("I run actor add with missing config path")
def step_impl(context):
missing_path = Path(tempfile.gettempdir()) / "missing-actor-config.json"
with patch("cleveragents.application.container.get_container") as mock_container:
mock_container.return_value.actor_service.return_value = MagicMock()
mock_container.return_value.actor_registry.return_value = MagicMock()
context.result = context.runner.invoke(
actor_app,
[
"add",
"test-actor",
"--config",
str(missing_path),
],
)
context.expected_error = "Config file not found"
@when("I run actor add with business rule violation")
def step_impl(context):
config_data = {"provider": "openai", "model": "gpt-4o"}
handle = tempfile.NamedTemporaryFile(
delete=False, suffix=".json", mode="w", encoding="utf-8"
)
json.dump(config_data, handle)
handle.flush()
handle.close()
config_path = Path(handle.name)
_register_cleanup(context, config_path)
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_registry = MagicMock()
mock_actor_registry.upsert_actor.side_effect = BusinessRuleViolation("invalid")
mock_container.return_value.actor_service.return_value = MagicMock()
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(
actor_app,
[
"add",
"test-actor",
"--config",
str(config_path),
],
)
context.expected_error = "Error:"
@when("I run actor add via service without unsafe flag")
def step_impl(context):
class ServiceOnlyContainer:
def __init__(self, actor_service: MagicMock):
self._actor_service = actor_service
def actor_service(self):
return self._actor_service
actor_service = MagicMock()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_container.return_value = ServiceOnlyContainer(actor_service)
context.result = context.runner.invoke(
actor_app,
[
"add",
"service/unsafe",
"--config",
str(context.actor_config_path),
],
)
context.actor_service = actor_service
context.expected_error = "Actor config is marked unsafe"
@when("I run actor add via service with unsafe canonical blob")
def step_impl(context):
class ServiceOnlyContainer:
def __init__(self, actor_service: MagicMock):
self._actor_service = actor_service
def actor_service(self):
return self._actor_service
actor_service = MagicMock()
resolved = MagicMock(
provider="openai",
model="gpt-4o",
graph_descriptor=None,
unsafe=True,
options=None,
)
canonical_blob = {
"provider": resolved.provider,
"model": resolved.model,
"unsafe": True,
}
with (
patch(
"cleveragents.cli.commands.actor._canonicalize_actor_config",
return_value=(resolved, canonical_blob, False),
),
patch("cleveragents.application.container.get_container") as mock_container,
):
mock_container.return_value = ServiceOnlyContainer(actor_service)
context.result = context.runner.invoke(
actor_app,
[
"add",
"service/unsafe",
"--config",
str(context.actor_config_path),
],
)
context.actor_service = actor_service
context.expected_error = "Actor config is marked unsafe"
@when("I run actor update with safe flag and yaml config")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_registry = MagicMock()
mock_actor_service = MagicMock()
current_actor = _make_actor(
name="local/existing-actor",
provider="existing-provider",
model="existing-model",
config={"existing": True},
unsafe=True,
)
mock_actor_registry.get_actor.return_value = current_actor
updated_actor = _make_actor(
name=current_actor.name,
provider=current_actor.provider,
model=current_actor.model,
config=context.actor_config_data,
unsafe=False,
)
mock_actor_registry.upsert_actor.return_value = updated_actor
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(
actor_app,
[
"update",
current_actor.name,
"--config",
str(context.actor_config_path),
"--safe",
],
)
context.mock_actor_registry = mock_actor_registry
context.current_actor = current_actor
@when("I run actor update with unsafe flag")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_registry = MagicMock()
mock_actor_service = MagicMock()
current_actor = _make_actor(
name="local/unsafe-actor",
provider="unsafe-provider",
model="unsafe-model",
config={"existing": True},
unsafe=False,
)
mock_actor_registry.get_actor.return_value = current_actor
updated_actor = _make_actor(
name=current_actor.name,
provider=current_actor.provider,
model=current_actor.model,
config=current_actor.config_blob,
unsafe=True,
)
mock_actor_registry.upsert_actor.return_value = updated_actor
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(
actor_app,
[
"update",
current_actor.name,
"--unsafe",
],
)
context.mock_actor_registry = mock_actor_registry
context.current_actor = current_actor
@when("I run actor update with validation error")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
current_actor = _make_actor(name="local/invalid-update")
mock_actor_registry.get_actor.return_value = current_actor
mock_actor_registry.upsert_actor.side_effect = ValidationError("bad update")
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(
actor_app,
[
"update",
current_actor.name,
],
)
context.mock_actor_registry = mock_actor_registry
context.expected_error = "Error:"
@when("I run actor update for missing actor")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
mock_actor_registry.get_actor.side_effect = NotFoundError(
resource_type="actor", resource_id="local/missing"
)
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(
actor_app,
[
"update",
"missing-actor",
],
)
context.mock_actor_registry = mock_actor_registry
context.expected_error = "Actor not found"
@when("I run actor update with conflicting flags")
def step_impl(context):
context.result = context.runner.invoke(
actor_app,
[
"update",
"local/conflict",
"--unsafe",
"--safe",
],
)
context.expected_error = "Choose only one of --unsafe or --safe"
@when("I run actor update requiring unsafe confirmation")
def step_impl(context):
with (
patch("cleveragents.application.container.get_container") as mock_container,
patch(
"cleveragents.cli.commands.actor._canonicalize_actor_config"
) as mock_canonicalize,
):
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
current_actor = _make_actor(name="local/needs-confirm")
mock_actor_registry.get_actor.return_value = current_actor
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
mock_resolved = MagicMock(unsafe=True)
mock_canonicalize.return_value = (
mock_resolved,
{"provider": current_actor.provider, "model": current_actor.model},
True,
)
context.result = context.runner.invoke(
actor_app, ["update", current_actor.name]
)
context.expected_error = "Actor config is marked unsafe"
@when("I run actor update via service without unsafe flag")
def step_impl(context):
class ServiceOnlyContainer:
def __init__(self, actor_service: MagicMock):
self._actor_service = actor_service
def actor_service(self):
return self._actor_service
actor_service = MagicMock()
current_actor = _make_actor(
name="local/service-update",
provider="service-provider",
model="service-model",
config={"provider": "service-provider", "model": "service-model"},
)
actor_service.get_actor.return_value = current_actor
with patch("cleveragents.application.container.get_container") as mock_container:
mock_container.return_value = ServiceOnlyContainer(actor_service)
context.result = context.runner.invoke(
actor_app,
[
"update",
current_actor.name,
"--config",
str(context.actor_config_path),
],
)
context.actor_service = actor_service
context.expected_error = "Actor config is marked unsafe"
@when("I run actor update with option overrides")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
current_actor = _make_actor(
name="local/option-update",
provider="registry-provider",
model="registry-model",
config={
"provider": "registry-provider",
"model": "registry-model",
"options": {"keep": "existing"},
},
)
mock_actor_registry.get_actor.return_value = current_actor
updated_actor = _make_actor(
name=current_actor.name,
provider=current_actor.provider,
model=current_actor.model,
config=current_actor.config_blob,
)
mock_actor_registry.upsert_actor.return_value = updated_actor
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(
actor_app,
[
"update",
current_actor.name,
"--option",
"retries=2",
"--option",
"enabled=false",
],
)
context.current_actor = current_actor
context.mock_actor_registry = mock_actor_registry
context.expected_option_overrides = {"retries": 2, "enabled": False}
@when("I run actor update via service with unsafe canonical blob")
def step_impl(context):
class ServiceOnlyContainer:
def __init__(self, actor_service: MagicMock):
self._actor_service = actor_service
def actor_service(self):
return self._actor_service
actor_service = MagicMock()
current_actor = _make_actor(
name="local/service-canonical",
provider="service-provider",
model="service-model",
config={"provider": "service-provider", "model": "service-model"},
)
actor_service.get_actor.return_value = current_actor
resolved = MagicMock(
provider=current_actor.provider,
model=current_actor.model,
graph_descriptor=current_actor.graph_descriptor,
unsafe=True,
options=None,
)
canonical_blob = {
"provider": resolved.provider,
"model": resolved.model,
"unsafe": True,
}
with (
patch(
"cleveragents.cli.commands.actor._canonicalize_actor_config",
return_value=(resolved, canonical_blob, False),
),
patch("cleveragents.application.container.get_container") as mock_container,
):
mock_container.return_value = ServiceOnlyContainer(actor_service)
context.result = context.runner.invoke(
actor_app,
[
"update",
current_actor.name,
"--config",
str(context.actor_config_path),
],
)
context.actor_service = actor_service
context.expected_error = "Actor config is marked unsafe"
@when("I run actor remove successfully")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(actor_app, ["remove", "local/removable"])
context.mock_actor_registry = mock_actor_registry
@when("I run actor remove and it fails validation")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
mock_actor_registry.remove_actor.side_effect = ValidationError("invalid")
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(actor_app, ["remove", "local/removable"])
context.mock_actor_registry = mock_actor_registry
context.expected_error = "Error:"
@when("I run actor remove via service path")
def step_impl(context):
class ServiceOnlyContainer:
def __init__(self, actor_service: MagicMock):
self._actor_service = actor_service
def actor_service(self):
return self._actor_service
actor_service = MagicMock()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_container.return_value = ServiceOnlyContainer(actor_service)
context.result = context.runner.invoke(actor_app, ["remove", "local/removable"])
context.actor_service = actor_service
@when("I run actor list with no actors")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
mock_actor_registry.list_actors.return_value = []
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(actor_app, ["list"])
@when("I run actor list with two actors")
def step_impl(context):
actors = [
_make_actor(name="local/first", provider="p1", model="m1"),
_make_actor(name="local/second", provider="p2", model="m2", unsafe=True),
]
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
mock_actor_registry.list_actors.return_value = actors
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(actor_app, ["list"])
context.actors = actors
@when("I run actor show successfully")
def step_impl(context):
actor = _make_actor(name="local/show", provider="provider", model="model")
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
mock_actor_registry.get_actor.return_value = actor
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(actor_app, ["show", actor.name])
context.actor = actor
@when("I run actor show with validation error")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
mock_actor_registry.get_actor.side_effect = ValidationError("bad")
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(actor_app, ["show", "local/show"])
context.expected_error = "Error:"
@when("I run set-default actor successfully")
def step_impl(context):
actor = _make_actor(name="provider/model", provider="provider", model="model")
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
mock_actor_registry.set_default_actor.return_value = actor
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(actor_app, ["set-default", actor.name])
context.actor = actor
@when("I run set-default actor with violation")
def step_impl(context):
with patch("cleveragents.application.container.get_container") as mock_container:
mock_actor_service = MagicMock()
mock_actor_registry = MagicMock()
mock_actor_registry.set_default_actor.side_effect = BusinessRuleViolation(
"nope"
)
mock_container.return_value.actor_service.return_value = mock_actor_service
mock_container.return_value.actor_registry.return_value = mock_actor_registry
context.result = context.runner.invoke(actor_app, ["set-default", "local/fail"])
context.expected_error = "Error:"
@then("the actor add should succeed with default config")
def step_impl(context):
assert context.result.exit_code == 0
context.mock_actor_registry.upsert_actor.assert_called_once()
@then("the actor add should pass the loaded config")
def step_impl(context):
assert context.result.exit_code == 0
context.mock_actor_registry.upsert_actor.assert_called_once()
call_kwargs = context.mock_actor_registry.upsert_actor.call_args.kwargs
assert call_kwargs.get("config_blob") == context.expected_config_blob
expected_allow = getattr(context, "expected_allow_unsafe", False)
assert call_kwargs.get("allow_unsafe") == expected_allow
@then("the service actor add should include graph descriptor")
def step_impl(context):
assert context.result.exit_code == 0
context.actor_service.upsert_actor.assert_called_once()
call_kwargs = context.actor_service.upsert_actor.call_args.kwargs
assert call_kwargs.get("graph_descriptor") == context.graph_descriptor
assert (
call_kwargs.get("config_blob", {}).get("graph_descriptor")
== context.graph_descriptor
)
@then("the actor command should fail with bad parameter")
def step_impl(context):
assert context.result.exit_code != 0
expected = getattr(context, "expected_error", None)
if expected:
assert expected in context.result.output
@then("the actor update should set safe and merge config")
def step_impl(context):
assert context.result.exit_code == 0
expected_config = dict(context.actor_config_data)
expected_config.setdefault("unsafe", False)
context.mock_actor_registry.upsert_actor.assert_called_once_with(
name=context.current_actor.name,
provider=None,
model=None,
config_blob=expected_config,
graph_descriptor=context.current_actor.graph_descriptor,
unsafe=False,
set_default=False,
is_built_in=context.current_actor.is_built_in,
allow_unsafe=False,
)
@then("the actor update should set unsafe flag")
def step_impl(context):
assert context.result.exit_code == 0
expected_config = dict(context.current_actor.config_blob)
expected_config.setdefault("provider", context.current_actor.provider)
expected_config.setdefault("model", context.current_actor.model)
expected_config.setdefault("unsafe", True)
context.mock_actor_registry.upsert_actor.assert_called_once_with(
name=context.current_actor.name,
provider=context.current_actor.provider,
model=context.current_actor.model,
config_blob=expected_config,
graph_descriptor=context.current_actor.graph_descriptor,
unsafe=True,
set_default=False,
is_built_in=context.current_actor.is_built_in,
allow_unsafe=True,
)
@then("the actor update should include option overrides")
def step_impl(context):
assert context.result.exit_code == 0
context.mock_actor_registry.upsert_actor.assert_called_once()
call_kwargs = context.mock_actor_registry.upsert_actor.call_args.kwargs
assert call_kwargs.get("option_overrides") == context.expected_option_overrides
options_blob = call_kwargs.get("config_blob", {}).get("options", {})
for key, value in context.expected_option_overrides.items():
assert options_blob.get(key) == value
@then("the actor command should abort for missing actor")
def step_impl(context):
assert context.result.exit_code != 0
assert context.expected_error in context.result.output
@then("the actor remove should succeed")
def step_impl(context):
assert context.result.exit_code == 0
context.mock_actor_registry.remove_actor.assert_called_once_with("local/removable")
@then("the actor service remove should be called")
def step_impl(context):
assert context.result.exit_code == 0
context.actor_service.remove_actor.assert_called_once_with("local/removable")
@then("the actor command should abort with error")
def step_impl(context):
assert context.result.exit_code != 0
expected = getattr(context, "expected_error", None)
if expected:
assert expected in context.result.output
@then("the actor service should not be called")
def step_impl(context):
assert context.actor_service.upsert_actor.call_count == 0
@then("the actor list should report empty state")
def step_impl(context):
assert context.result.exit_code == 0
assert "No actors configured." in context.result.output
@then("the actor list should render rows")
def step_impl(context):
assert context.result.exit_code == 0
for actor in context.actors:
assert actor.name in context.result.output
@then("the actor show should display details")
def step_impl(context):
assert context.result.exit_code == 0
assert context.actor.name in context.result.output
@then("the set default actor should display details")
def step_impl(context):
assert context.result.exit_code == 0
assert context.actor.name in context.result.output