Files
cleveragents-core/features/steps/actor_config_steps.py
T
freemo 983eee7a37
CI / lint (pull_request) Successful in 14s
CI / typecheck (pull_request) Successful in 28s
CI / security (pull_request) Successful in 23s
CI / quality (pull_request) Successful in 15s
CI / integration_tests (pull_request) Successful in 4m53s
CI / build (pull_request) Successful in 16s
CI / coverage (pull_request) Successful in 7m42s
CI / unit_tests (pull_request) Successful in 13m36s
CI / docker (pull_request) Successful in 40s
CI / lint (push) Successful in 14s
CI / typecheck (push) Successful in 28s
CI / security (push) Successful in 23s
CI / quality (push) Successful in 16s
CI / integration_tests (push) Successful in 4m54s
CI / build (push) Successful in 16s
CI / coverage (push) Successful in 7m46s
CI / unit_tests (push) Successful in 13m46s
CI / docker (push) Successful in 41s
Fix: Fixed failing unit tests due to outdated behave version and alembic typo, and other fixes.
2026-02-16 14:21:06 -05:00

284 lines
10 KiB
Python

"""Step definitions for actor configuration coverage."""
from __future__ import annotations
import ast
import json
import os
import shutil
import tempfile
from collections.abc import Callable
from pathlib import Path
from typing import Any
from behave import given, then, when
import cleveragents.actor.config as actor_config
from cleveragents.actor.config import ActorConfiguration
def _add_cleanup(context, handler: Callable[[], None]) -> None:
if not hasattr(context, "_cleanup_handlers"):
context._cleanup_handlers = []
context._cleanup_handlers.append(handler)
def _ensure_workspace(context) -> Path:
if getattr(context, "config_workspace", None) is None:
workspace = Path(tempfile.mkdtemp(prefix="actor-config-"))
context.config_workspace = workspace
def cleanup_workspace() -> None:
shutil.rmtree(workspace, ignore_errors=True)
_add_cleanup(context, cleanup_workspace)
return context.config_workspace
def _resolve_path(obj: Any, path: str) -> Any:
current: Any = obj
for segment in path.split("."):
if isinstance(current, list):
current = current[int(segment)]
continue
if isinstance(current, dict):
assert segment in current, f"{segment} missing from {current}"
current = current[segment]
continue
raise AssertionError(f"Cannot traverse through {type(current).__name__}")
return current
@given("an isolated actor config workspace")
def step_isolated_workspace(context) -> None:
_ensure_workspace(context)
@given('an actor config file "{filename}" with content:')
def step_actor_config_file_with_content(context, filename: str) -> None:
workspace = _ensure_workspace(context)
path = workspace / filename
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(context.text or "")
context.last_config_path = path
@given("PyYAML parsing is unavailable for actor config")
def step_disable_yaml(context) -> None:
context._original_yaml_module = actor_config.yaml
actor_config.yaml = None
def restore_yaml() -> None:
actor_config.yaml = context._original_yaml_module
_add_cleanup(context, restore_yaml)
@given('the actor config environment variable "{name}" is unset')
def step_unset_env_var(context, name: str) -> None:
original = os.environ.get(name)
def restore() -> None:
if original is None:
os.environ.pop(name, None)
else:
os.environ[name] = original
_add_cleanup(context, restore)
os.environ.pop(name, None)
@given('the actor config environment variable "{name}" is set to "{value}"')
def step_set_env_var(context, name: str, value: str) -> None:
original = os.environ.get(name)
def restore() -> None:
if original is None:
os.environ.pop(name, None)
else:
os.environ[name] = original
_add_cleanup(context, restore)
os.environ[name] = value
@when('I load the actor config blob from "{filename}"')
def step_load_actor_config_blob(context, filename: str) -> None:
workspace = _ensure_workspace(context)
path = workspace / filename
try:
context.load_result = ActorConfiguration.load_blob_from_file(path)
context.last_error = None
except Exception as exc: # pragma: no cover - exercised in tests
context.load_result = None
context.last_error = exc
@when('I parse the actor configuration from file "{filename}" with overrides:')
def step_parse_actor_config_from_file(context, filename: str) -> None:
workspace = _ensure_workspace(context)
overrides: dict[str, Any] = json.loads(context.text or "{}")
try:
context.actor_config_result = ActorConfiguration.from_file(
path=workspace / filename,
name=overrides.get("name"),
provider=overrides.get("provider"),
model=overrides.get("model"),
graph_descriptor=overrides.get("graph_descriptor"),
unsafe=bool(overrides.get("unsafe", False)),
)
context.last_error = None
except Exception as exc: # pragma: no cover - exercised in tests
context.actor_config_result = None
context.last_error = exc
@when("I build an actor configuration from blob {blob_literal}")
def step_build_actor_config_from_blob(context, blob_literal: str) -> None:
blob = ast.literal_eval(blob_literal)
try:
context.actor_config_result = ActorConfiguration.from_blob(blob=blob)
context.last_error = None
except Exception as exc: # pragma: no cover - exercised in tests
context.actor_config_result = None
context.last_error = exc
@when(
"I build an actor configuration from structured blob with defaults and overrides:"
)
def step_build_actor_config_with_defaults(context) -> None:
payload = json.loads(context.text or "{}")
blob = payload.get("blob")
default_options = payload.get("default_options")
option_overrides = payload.get("option_overrides")
try:
context.actor_config_result = ActorConfiguration.from_blob(
blob=blob,
default_options=default_options,
option_overrides=option_overrides,
)
context.last_error = None
except Exception as exc: # pragma: no cover - exercised in tests
context.actor_config_result = None
context.last_error = exc
@then('a ValueError should be raised containing "{message}"')
def step_value_error_with_message(context, message: str) -> None:
assert isinstance(context.last_error, ValueError), type(context.last_error)
assert message in str(context.last_error)
@then("the loaded actor config blob should equal {expected}")
def step_loaded_blob_equals(context, expected: str) -> None:
assert context.last_error is None, context.last_error
assert context.load_result == ast.literal_eval(expected)
@then('the loaded actor config value at "{path_expr}" should equal {expected}')
def step_loaded_blob_value_at_path(context, path_expr: str, expected: str) -> None:
assert context.last_error is None, context.last_error
assert context.load_result is not None
actual = _resolve_path(context.load_result, path_expr)
assert actual == ast.literal_eval(expected)
@then('the actor configuration should have provider "{provider}" and model "{model}"')
def step_actor_config_provider_model(context, provider: str, model: str) -> None:
assert context.last_error is None, context.last_error
assert context.actor_config_result is not None
assert context.actor_config_result.provider == provider
assert context.actor_config_result.model == model
@then('the actor configuration name should be "{name}"')
def step_actor_config_name(context, name: str) -> None:
assert context.last_error is None, context.last_error
assert context.actor_config_result is not None
assert context.actor_config_result.name == name
@then("the actor configuration graph descriptor should equal {expected}")
def step_actor_config_graph_descriptor(context, expected: str) -> None:
assert context.last_error is None, context.last_error
assert context.actor_config_result is not None
assert context.actor_config_result.graph_descriptor == ast.literal_eval(expected)
@then('the actor configuration graph descriptor should include key "{key}"')
def step_actor_config_graph_descriptor_has_key(context, key: str) -> None:
assert context.last_error is None, context.last_error
assert context.actor_config_result is not None
graph_descriptor = context.actor_config_result.graph_descriptor
assert graph_descriptor is not None, "graph_descriptor missing"
assert key in graph_descriptor, f"{key} missing from graph_descriptor"
@then("the actor configuration options should equal {expected}")
def step_actor_config_options(context, expected: str) -> None:
assert context.last_error is None, context.last_error
assert context.actor_config_result is not None
assert context.actor_config_result.options == ast.literal_eval(expected)
@then('the loaded actor config value at "{path_expr}" should contain "{substring}"')
def step_loaded_blob_value_contains(context, path_expr: str, substring: str) -> None:
assert context.last_error is None, context.last_error
assert context.load_result is not None
actual = _resolve_path(context.load_result, path_expr)
assert isinstance(actual, str), f"Expected str, got {type(actual).__name__}"
assert substring in actual, f"'{substring}' not found in '{actual}'"
@then('the loaded actor config nested value at "{path_expr}" should equal {expected}')
def step_loaded_blob_nested_value(context, path_expr: str, expected: str) -> None:
assert context.last_error is None, context.last_error
assert context.load_result is not None
actual = _resolve_path(context.load_result, path_expr)
assert actual == ast.literal_eval(expected), f"Expected {expected}, got {actual!r}"
@when("I build actor config from None blob with provider and model overrides")
def step_build_actor_config_none_blob(context) -> None:
try:
context.actor_config_result = ActorConfiguration.from_blob(
blob=None,
provider="override-provider",
model="override-model",
)
context.last_error = None
except Exception as exc: # pragma: no cover - exercised in tests
context.actor_config_result = None
context.last_error = exc
@when("I build actor config from blob using graph key alias")
def step_build_actor_config_graph_alias(context) -> None:
try:
context.actor_config_result = ActorConfiguration.from_blob(
blob={
"provider": "p",
"model": "m",
"graph": {"step": "one"},
},
)
context.last_error = None
except Exception as exc: # pragma: no cover - exercised in tests
context.actor_config_result = None
context.last_error = exc
@then("the actor configuration graph descriptor should be None")
def step_actor_config_graph_descriptor_none(context) -> None:
assert context.last_error is None, context.last_error
assert context.actor_config_result is not None
assert context.actor_config_result.graph_descriptor is None
@then("the actor configuration unsafe flag should be true")
def step_actor_config_unsafe_true(context) -> None:
assert context.last_error is None, context.last_error
assert context.actor_config_result is not None
assert context.actor_config_result.unsafe is True