Files
temp/features/steps/actor_cli_run_steps.py
T
brent.edwards 7ef5ebb695 feat: Add Q0: Pre-commit hooks setup.
This should automatically check for problems on build.
2026-02-10 16:04:17 +00:00

661 lines
20 KiB
Python

"""Step definitions for actor CLI run coverage."""
from __future__ import annotations
import json
import tempfile
from pathlib import Path
from types import SimpleNamespace
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from behave import given, then, when
from cleveragents.cli.commands.actor import app as actor_app
from cleveragents.cli.commands.actor_run import app as actor_run_app
from cleveragents.core.exceptions import (
CleverAgentsError,
CleverAgentsException,
UnsafeConfigurationError,
)
def _register_cleanup(context, path: Path) -> None:
context._cleanup_handlers.append(lambda: path.unlink(missing_ok=True))
def _make_app(
*,
result: str,
config_global_context: dict[str, Any] | None = None,
run_side_effect: Exception | None = None,
) -> MagicMock:
app_exec = MagicMock()
app_exec.config = SimpleNamespace(global_context=dict(config_global_context or {}))
app_exec.run_single_shot = AsyncMock(return_value=result)
if run_side_effect is not None:
app_exec.run_single_shot.side_effect = run_side_effect
return app_exec
def _make_context_manager(
*,
global_context: dict[str, Any] | None = None,
exists: bool = False,
) -> MagicMock:
ctx_mgr = MagicMock()
ctx_mgr.global_context = dict(global_context or {})
ctx_mgr.exists.return_value = exists
return ctx_mgr
@given("I have a saved context JSON file")
def step_saved_context_json(context):
context.load_context_data = {"global_context": {"session": "stored"}}
with tempfile.NamedTemporaryFile(
delete=False, suffix=".json", mode="w", encoding="utf-8"
) as handle:
json.dump(context.load_context_data, handle)
handle.flush()
context.load_context_path = Path(handle.name)
_register_cleanup(context, context.load_context_path)
@given("I have an actor output file path")
def step_actor_output_path(context):
with tempfile.NamedTemporaryFile(
delete=False, suffix=".txt", mode="w", encoding="utf-8"
) as handle:
pass
context.output_path = Path(handle.name)
_register_cleanup(context, context.output_path)
@when("I run actor run with load context and context name")
def step_run_actor_with_load_and_context(context):
context.prompt = "hello from context"
context.run_result = "actor response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
ctx_mgr = _make_context_manager(global_context={"from": "context"})
with (
patch(
"cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
return_value=app_exec,
),
patch(
"cleveragents.cli.commands.actor.ContextManager",
return_value=ctx_mgr,
),
):
context.result = context.runner.invoke(
actor_app,
[
"run",
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
"--context",
"session-1",
"--load-context",
str(context.load_context_path),
"--output",
str(context.output_path),
],
)
context.app_exec = app_exec
context.ctx_mgr = ctx_mgr
@when("I run actor run with load context only")
def step_run_actor_with_load_only(context):
context.prompt = "load-only"
context.run_result = "loaded context response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
with patch(
"cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
return_value=app_exec,
):
context.result = context.runner.invoke(
actor_app,
[
"run",
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
"--load-context",
str(context.load_context_path),
],
)
context.app_exec = app_exec
@when("I run actor run with context only")
def step_run_actor_with_context_only(context):
context.prompt = "context-only"
context.run_result = "context response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
ctx_mgr = _make_context_manager(
global_context={"cached": "value"},
exists=True,
)
with (
patch(
"cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
return_value=app_exec,
),
patch(
"cleveragents.cli.commands.actor.ContextManager",
return_value=ctx_mgr,
),
):
context.result = context.runner.invoke(
actor_app,
[
"run",
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
"--context",
"session-2",
],
)
context.app_exec = app_exec
context.ctx_mgr = ctx_mgr
@when("I run actor run without context")
def step_run_actor_without_context(context):
context.prompt = "no-context"
context.run_result = "direct response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
with patch(
"cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
return_value=app_exec,
):
context.result = context.runner.invoke(
actor_app,
[
"run",
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
],
)
context.app_exec = app_exec
@when("I run actor run with load context and context name allowing rxpy")
def step_run_actor_with_load_context_and_rxpy(context):
context.prompt = "rxpy-context"
context.run_result = "rxpy response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
ctx_mgr = _make_context_manager(global_context={"from": "context"})
with (
patch(
"cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
return_value=app_exec,
),
patch(
"cleveragents.cli.commands.actor.ContextManager",
return_value=ctx_mgr,
),
):
context.result = context.runner.invoke(
actor_app,
[
"run",
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
"--context",
"session-rxpy",
"--load-context",
str(context.load_context_path),
"--output",
str(context.output_path),
"--allow-rxpy-in-run-mode",
],
)
context.app_exec = app_exec
context.ctx_mgr = ctx_mgr
@when("I run actor run with load context only allowing rxpy")
def step_run_actor_with_load_only_rxpy(context):
context.prompt = "rxpy-load-only"
context.run_result = "rxpy load response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
with patch(
"cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
return_value=app_exec,
):
context.result = context.runner.invoke(
actor_app,
[
"run",
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
"--load-context",
str(context.load_context_path),
"--allow-rxpy-in-run-mode",
],
)
context.app_exec = app_exec
@when("I run actor run with context only allowing rxpy")
def step_run_actor_with_context_only_rxpy(context):
context.prompt = "rxpy-context-only"
context.run_result = "rxpy context response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
ctx_mgr = _make_context_manager(
global_context={"cached": "value"},
exists=True,
)
with (
patch(
"cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
return_value=app_exec,
),
patch(
"cleveragents.cli.commands.actor.ContextManager",
return_value=ctx_mgr,
),
):
context.result = context.runner.invoke(
actor_app,
[
"run",
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
"--context",
"session-rxpy-only",
"--allow-rxpy-in-run-mode",
],
)
context.app_exec = app_exec
context.ctx_mgr = ctx_mgr
@when("I run actor run without context allowing rxpy")
def step_run_actor_without_context_rxpy(context):
context.prompt = "rxpy-no-context"
context.run_result = "rxpy direct response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
with patch(
"cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
return_value=app_exec,
):
context.result = context.runner.invoke(
actor_app,
[
"run",
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
"--allow-rxpy-in-run-mode",
],
)
context.app_exec = app_exec
@when("I run actor run with unsafe configuration error")
def step_run_actor_unsafe_error(context):
context.prompt = "unsafe"
error = UnsafeConfigurationError("unsafe config")
app_exec = _make_app(
result="unused",
config_global_context={"existing": "value"},
run_side_effect=error,
)
with patch(
"cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
return_value=app_exec,
):
context.result = context.runner.invoke(
actor_app,
[
"run",
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
],
)
context.expected_error = "Error: unsafe config"
context.expected_exit_code = 1
@when("I run actor run with clever agents error")
def step_run_actor_clever_agents_error(context):
context.prompt = "failed"
error = CleverAgentsError("request failed")
app_exec = _make_app(
result="unused",
config_global_context={"existing": "value"},
run_side_effect=error,
)
with patch(
"cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
return_value=app_exec,
):
context.result = context.runner.invoke(
actor_app,
[
"run",
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
],
)
context.expected_error = "Error: request failed"
context.expected_exit_code = 2
@when("I invoke the actor-run command with load context and a context name")
def step_invoke_actor_run_with_load_and_context(context):
context.prompt = "hello from actor-run"
context.run_result = "actor-run response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
ctx_mgr = _make_context_manager(global_context={"from": "context"})
with (
patch(
"cleveragents.cli.commands.actor_run.ReactiveCleverAgentsApp",
return_value=app_exec,
),
patch(
"cleveragents.cli.commands.actor_run.ContextManager",
return_value=ctx_mgr,
),
):
context.result = context.runner.invoke(
actor_run_app,
[
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
"--context",
"actor-run-session",
"--load-context",
str(context.load_context_path),
"--output",
str(context.output_path),
],
)
context.app_exec = app_exec
context.ctx_mgr = ctx_mgr
@when("I invoke the actor-run command with load context only")
def step_invoke_actor_run_with_load_only(context):
context.prompt = "actor-run load-only"
context.run_result = "actor-run loaded response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
with patch(
"cleveragents.cli.commands.actor_run.ReactiveCleverAgentsApp",
return_value=app_exec,
):
context.result = context.runner.invoke(
actor_run_app,
[
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
"--load-context",
str(context.load_context_path),
],
)
context.app_exec = app_exec
@when("I invoke the actor-run command with context only")
def step_invoke_actor_run_with_context_only(context):
context.prompt = "actor-run context-only"
context.run_result = "actor-run context response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
ctx_mgr = _make_context_manager(
global_context={"cached": "value"},
exists=True,
)
with (
patch(
"cleveragents.cli.commands.actor_run.ReactiveCleverAgentsApp",
return_value=app_exec,
),
patch(
"cleveragents.cli.commands.actor_run.ContextManager",
return_value=ctx_mgr,
),
):
context.result = context.runner.invoke(
actor_run_app,
[
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
"--context",
"actor-run-session-2",
],
)
context.app_exec = app_exec
context.ctx_mgr = ctx_mgr
@when("I invoke the actor-run command without any context")
def step_invoke_actor_run_without_context(context):
context.prompt = "actor-run no context"
context.run_result = "actor-run direct response"
app_exec = _make_app(
result=context.run_result,
config_global_context={"existing": "value"},
)
with patch(
"cleveragents.cli.commands.actor_run.ReactiveCleverAgentsApp",
return_value=app_exec,
):
context.result = context.runner.invoke(
actor_run_app,
[
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
],
)
context.app_exec = app_exec
@when("I invoke the actor-run command with an unsafe configuration error")
def step_invoke_actor_run_unsafe_error(context):
context.prompt = "actor-run unsafe"
error = UnsafeConfigurationError("unsafe config")
app_exec = _make_app(
result="unused",
config_global_context={"existing": "value"},
run_side_effect=error,
)
with patch(
"cleveragents.cli.commands.actor_run.ReactiveCleverAgentsApp",
return_value=app_exec,
):
context.result = context.runner.invoke(
actor_run_app,
[
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
],
)
context.expected_error = "Error: unsafe config"
context.expected_exit_code = 1
@when("I invoke the actor-run command with a clever agents exception")
def step_invoke_actor_run_clever_agents_exception(context):
context.prompt = "actor-run failed"
error = CleverAgentsException("request failed")
app_exec = _make_app(
result="unused",
config_global_context={"existing": "value"},
run_side_effect=error,
)
with patch(
"cleveragents.cli.commands.actor_run.ReactiveCleverAgentsApp",
return_value=app_exec,
):
context.result = context.runner.invoke(
actor_run_app,
[
"--config",
str(context.actor_config_path),
"--prompt",
context.prompt,
],
)
context.expected_error = "Error: request failed"
context.expected_exit_code = 2
@then("the actor run should write output and persist context")
def step_actor_run_writes_output(context):
assert context.result.exit_code == 0
assert context.output_path.read_text() == context.run_result
assert f"Output written to {context.output_path}" in context.result.output
context.ctx_mgr.import_context.assert_called_once_with(context.load_context_path)
context.ctx_mgr.add_message.assert_any_call("user", context.prompt)
context.ctx_mgr.add_message.assert_any_call("assistant", context.run_result)
context.ctx_mgr.save_global_context.assert_called_once_with(
context.app_exec.config.global_context
)
assert context.app_exec.config.global_context.get("from") == "context"
@then("the actor run should update global context and echo result")
def step_actor_run_updates_global_context(context):
assert context.result.exit_code == 0
assert context.run_result in context.result.output
for key, value in context.load_context_data["global_context"].items():
assert context.app_exec.config.global_context.get(key) == value
call_kwargs = context.app_exec.run_single_shot.call_args.kwargs
assert call_kwargs.get("context_manager") is None
@then("the actor run should reuse saved context")
def step_actor_run_reuses_context(context):
assert context.result.exit_code == 0
context.ctx_mgr.exists.assert_called_once()
context.ctx_mgr.add_message.assert_any_call("user", context.prompt)
context.ctx_mgr.add_message.assert_any_call("assistant", context.run_result)
context.ctx_mgr.save_global_context.assert_called_once_with(
context.app_exec.config.global_context
)
call_kwargs = context.app_exec.run_single_shot.call_args.kwargs
assert call_kwargs.get("context_manager") == context.ctx_mgr
assert context.app_exec.config.global_context.get("cached") == "value"
@then("the actor run should call single shot without context manager")
def step_actor_run_single_shot_no_context(context):
assert context.result.exit_code == 0
assert context.run_result in context.result.output
call_kwargs = context.app_exec.run_single_shot.call_args.kwargs
assert call_kwargs.get("context_manager") is None
@then("the actor run should exit with error code 1")
def step_actor_run_exit_code_one(context):
assert context.result.exit_code == 1
assert context.expected_error in context.result.output
@then("the actor run should exit with error code 2")
def step_actor_run_exit_code_two(context):
assert context.result.exit_code == 2
assert context.expected_error in context.result.output
@then("the actor run should pass allow rxpy flag")
def step_actor_run_allow_rxpy_flag(context):
call_kwargs = context.app_exec.run_single_shot.call_args.kwargs
assert call_kwargs.get("allow_rxpy_in_run_mode") is True