"""Step definitions for actor add NAME positional argument 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 cleveragents.cli.commands.actor import app as actor_app from cleveragents.domain.models.core.actor import Actor def _make_actor( *, name: str = "local/test-actor", provider: str = "openai", model: str = "gpt-4o-mini", config: 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=None, unsafe=unsafe, is_built_in=is_built_in, is_default=is_default, ) def _register_cleanup(context: Any, path: Path) -> None: context._cleanup_handlers.append(lambda: path.unlink(missing_ok=True)) @given("I have an actor JSON config file without a name field") def step_impl(context: Any) -> None: context.actor_config_data = { "provider": "openai", "model": "gpt-4o-mini", "temperature": 0.5, } with tempfile.NamedTemporaryFile( delete=False, suffix=".json", mode="w", encoding="utf-8" ) as handle: json.dump(context.actor_config_data, handle) handle.flush() context.actor_config_path = Path(handle.name) _register_cleanup(context, context.actor_config_path) @given("I have an actor JSON config file with a different name") def step_impl(context: Any) -> None: context.actor_config_data = { "name": "local/config-name-actor", "provider": "openai", "model": "gpt-4o-mini", } with tempfile.NamedTemporaryFile( delete=False, suffix=".json", mode="w", encoding="utf-8" ) as handle: json.dump(context.actor_config_data, handle) handle.flush() context.actor_config_path = Path(handle.name) _register_cleanup(context, context.actor_config_path) @when("I run actor add with NAME positional argument and config") def step_impl(context: Any) -> None: context.positional_name = "local/my-actor" mock_actor = _make_actor(name=context.positional_name) with patch("cleveragents.cli.commands.actor._get_services") as mock_svc: registry = MagicMock() registry.upsert_actor.return_value = mock_actor mock_svc.return_value = (MagicMock(), registry) context.result = context.runner.invoke( actor_app, [ "add", context.positional_name, "--config", str(context.actor_config_path), ], ) context.mock_registry = registry @when("I run actor add with NAME positional argument overriding config name") def step_impl(context: Any) -> None: context.positional_name = "local/positional-name-actor" mock_actor = _make_actor(name=context.positional_name) with patch("cleveragents.cli.commands.actor._get_services") as mock_svc: registry = MagicMock() registry.upsert_actor.return_value = mock_actor mock_svc.return_value = (MagicMock(), registry) context.result = context.runner.invoke( actor_app, [ "add", context.positional_name, "--config", str(context.actor_config_path), ], ) context.mock_registry = registry @when("I run actor add with config but no NAME positional argument") def step_impl(context: Any) -> None: with patch("cleveragents.cli.commands.actor._get_services") as mock_svc: registry = MagicMock() mock_svc.return_value = (MagicMock(), registry) context.result = context.runner.invoke( actor_app, [ "add", "--config", str(context.actor_config_path), ], ) @then("the actor add should succeed with the positional name") def step_impl(context: Any) -> None: assert context.result.exit_code == 0, ( f"Expected exit_code=0, got {context.result.exit_code}.\n" f"Output:\n{context.result.output}" ) # Verify the registry was called with the positional name assert context.mock_registry.upsert_actor.called, ( "Expected upsert_actor to be called on the registry" ) call_kwargs = context.mock_registry.upsert_actor.call_args actual_name = call_kwargs.kwargs.get("name") or ( call_kwargs.args[0] if call_kwargs.args else None ) assert actual_name == context.positional_name, ( f"Expected upsert_actor called with name={context.positional_name!r}, " f"got name={actual_name!r}" ) @then("the actor add should use the positional NAME not the config name") def step_impl(context: Any) -> None: assert context.result.exit_code == 0, ( f"Expected exit_code=0, got {context.result.exit_code}.\n" f"Output:\n{context.result.output}" ) assert context.mock_registry.upsert_actor.called, ( "Expected upsert_actor to be called on the registry" ) call_kwargs = context.mock_registry.upsert_actor.call_args actual_name = call_kwargs.kwargs.get("name") or ( call_kwargs.args[0] if call_kwargs.args else None ) assert actual_name == context.positional_name, ( f"Expected upsert_actor called with positional name={context.positional_name!r}, " f"but got name={actual_name!r} (config name was 'local/config-name-actor')" ) @then("the actor command should fail with missing argument error") def step_impl(context: Any) -> None: assert context.result.exit_code != 0, ( f"Expected non-zero exit_code, got {context.result.exit_code}.\n" f"Output:\n{context.result.output}" )