Files
temp/features/steps/actor_registry_persistence_steps.py

255 lines
8.8 KiB
Python

"""Behave steps for actor registry persistence with YAML text retention."""
from __future__ import annotations
from typing import Any
from behave import given, then, when
from behave.runner import Context
from cleveragents.actor.registry import ActorRegistry
from cleveragents.config.settings import ProviderDefaults
from cleveragents.core.exceptions import ValidationError
from cleveragents.domain.models.core.actor import Actor
from cleveragents.providers.registry import ProviderInfo
class _StubSettings:
def __init__(self) -> None:
self._defaults = ProviderDefaults(
provider=None, provider_source="test", model=None, model_source="test"
)
def resolve_provider_defaults(self) -> ProviderDefaults:
return self._defaults
class _StubProviderRegistry:
def get_configured_providers(self) -> list[ProviderInfo]:
return []
class _StubActorService:
"""In-memory actor service stub for persistence tests."""
def __init__(self) -> None:
self.actors: dict[str, Actor] = {}
self.default_actor_name: str | None = None
def upsert_actor(
self,
*,
name: str,
provider: str,
model: str,
config_blob: dict[str, Any] | None = None,
graph_descriptor: dict[str, Any] | None = None,
unsafe: bool = False,
set_default: bool = False,
is_built_in: bool = False,
yaml_text: str | None = None,
schema_version: str | None = None,
compiled_metadata: dict[str, Any] | None = None,
) -> Actor:
blob = config_blob or {}
actor = Actor(
id=len(self.actors) + 1,
name=name,
provider=provider,
model=model,
config_blob=blob,
config_hash=Actor.compute_hash(blob),
graph_descriptor=graph_descriptor,
yaml_text=yaml_text,
schema_version=schema_version or "1.0",
compiled_metadata=compiled_metadata,
unsafe=unsafe,
is_built_in=is_built_in,
is_default=False,
)
self.actors[name] = actor
if set_default:
self.default_actor_name = name
return actor
def get_actor(self, name: str) -> Actor:
if name not in self.actors:
raise ValueError(f"Actor '{name}' not found")
return self.actors[name]
def list_actors(self) -> list[Actor]:
return list(self.actors.values())
def remove_actor(self, name: str) -> None:
if name not in self.actors:
raise ValueError(f"Actor '{name}' not found")
del self.actors[name]
if self.default_actor_name == name:
self.default_actor_name = None
def get_default_actor(self) -> Actor | None:
if self.default_actor_name and self.default_actor_name in self.actors:
return self.actors[self.default_actor_name]
return None
def set_default_actor(self, name: str) -> Actor:
actor = self.actors.get(name)
if actor is None:
raise ValueError("Actor does not exist")
self.default_actor_name = name
return actor
def _build_persistence_registry(context: Context) -> None:
context.actor_service = _StubActorService()
context.provider_registry = _StubProviderRegistry()
context.settings = _StubSettings()
context.registry = ActorRegistry(
actor_service=context.actor_service,
provider_registry=context.provider_registry,
settings=context.settings,
)
@given("an actor registry with no configured providers for persistence")
def step_registry_no_providers_persistence(context: Context) -> None:
_build_persistence_registry(context)
@when("I add actor from YAML text:")
def step_add_actor_yaml(context: Context) -> None:
yaml_text = context.text
context.added_actor = context.registry.add(yaml_text)
@when('I add actor from YAML text with schema_version "{version}":')
def step_add_actor_yaml_version(context: Context, version: str) -> None:
yaml_text = context.text
context.added_actor = context.registry.add(yaml_text, schema_version=version)
@when("I add actor from YAML text with compiled_metadata:")
def step_add_actor_yaml_compiled(context: Context) -> None:
yaml_text = context.text
metadata = {"graph_nodes": ["node1", "node2"], "tool_count": 3}
context.added_actor = context.registry.add(yaml_text, compiled_metadata=metadata)
@when("I update actor from YAML text:")
def step_update_actor_yaml(context: Context) -> None:
yaml_text = context.text
context.updated_actor = context.registry.add(yaml_text, update=True)
@when("I add actor from YAML text with update:")
def step_add_actor_yaml_with_update(context: Context) -> None:
yaml_text = context.text
context.added_actor = context.registry.add(yaml_text, update=True)
@when('I remove actor "{name}" via registry')
def step_remove_actor_via_registry(context: Context, name: str) -> None:
context.registry.remove(name)
@when("I attempt to add actor from YAML text without name:")
def step_attempt_add_no_name(context: Context) -> None:
yaml_text = context.text
try:
context.registry.add(yaml_text)
context.error = None
except (ValidationError, ValueError) as exc:
context.error = exc
@when("I attempt to add duplicate actor from YAML text:")
def step_attempt_add_duplicate(context: Context) -> None:
yaml_text = context.text
try:
context.registry.add(yaml_text, update=False)
context.error = None
except (ValidationError, ValueError) as exc:
context.error = exc
@when('I upsert actor "{name}" with yaml_text and schema_version "{version}"')
def step_upsert_legacy_yaml(context: Context, name: str, version: str) -> None:
context.saved_actor = context.registry.upsert_actor(
name=name,
config_blob={"provider": "openai", "model": "gpt-4"},
provider="openai",
model="gpt-4",
yaml_text="legacy yaml content",
schema_version=version,
)
@then('the actor "{name}" should have yaml_text containing "{substring}"')
def step_assert_yaml_text(context: Context, name: str, substring: str) -> None:
actor = context.actor_service.actors[name]
assert actor.yaml_text is not None, f"Actor {name} has no yaml_text"
assert substring in actor.yaml_text, (
f"Expected '{substring}' in yaml_text, got: {actor.yaml_text}"
)
@then('the actor "{name}" should have schema_version "{version}"')
def step_assert_schema_version(context: Context, name: str, version: str) -> None:
actor = context.actor_service.actors[name]
assert actor.schema_version == version, (
f"Expected schema_version '{version}', got: {actor.schema_version}"
)
@then('the actor "{name}" should have compiled_metadata key "{key}"')
def step_assert_compiled_metadata(context: Context, name: str, key: str) -> None:
actor = context.actor_service.actors[name]
assert actor.compiled_metadata is not None, f"Actor {name} has no compiled_metadata"
assert key in actor.compiled_metadata, (
f"Expected key '{key}' in compiled_metadata, "
f"got keys: {list(actor.compiled_metadata.keys())}"
)
@then('the actor "{name}" should not exist')
def step_assert_actor_not_exist(context: Context, name: str) -> None:
assert name not in context.actor_service.actors, f"Actor {name} still exists"
@then('the actor "{name}" should exist')
def step_assert_actor_exists(context: Context, name: str) -> None:
assert name in context.actor_service.actors, f"Actor {name} does not exist"
@then('listing actors with namespace "{namespace}" should return {count:d} actors')
def step_assert_namespace_filter(context: Context, namespace: str, count: int) -> None:
actors = context.registry.list(namespace=namespace)
assert len(actors) == count, (
f"Expected {count} actors in namespace '{namespace}', "
f"got {len(actors)}: {[a.name for a in actors]}"
)
@then('getting actor "{name}" via registry should return provider "{provider}"')
def step_assert_get_actor(context: Context, name: str, provider: str) -> None:
actor = context.registry.get(name)
assert actor.provider == provider, (
f"Expected provider '{provider}', got: {actor.provider}"
)
@then("a validation error should be raised for missing name")
def step_assert_missing_name_error(context: Context) -> None:
assert context.error is not None, "Expected a validation error"
assert "name" in str(context.error).lower(), (
f"Expected error about name, got: {context.error}"
)
@then("a validation error should be raised for duplicate actor")
def step_assert_duplicate_error(context: Context) -> None:
assert context.error is not None, "Expected a validation error"
assert "already exists" in str(context.error).lower(), (
f"Expected error about duplicate, got: {context.error}"
)