Files
temp/features/steps/actor_registry_new_coverage_steps.py
brent.edwards e801eb1ee8 feat(ci): add nox-based PR validation workflow
- Rewrite .forgejo/workflows/ci.yml to route all jobs through nox sessions
- Fix coverage_report nox session: serial behave mode replaces broken parallel
  mode (22% -> 97% accuracy), raise fail-under from 85% to 97%
- Pass posargs through format nox session for CI --check support
- Add 11 CI workflow validation scenarios (Behave) + Robot smoke test + ASV bench
- Add 108 new Behave scenarios covering 6 largest coverage gaps to reach 97%:
  yaml_template_engine, actor/config, actor/registry, message_router,
  context_analysis, context_service
- Update docs/development/ci-cd.md with nox-based CI docs and 97% threshold
- Restore implementation_plan.md verbose style, check off completed CI tasks

Verified: 1673 scenarios pass, 97% coverage, lint clean, typecheck clean
2026-02-12 22:01:51 +00:00

288 lines
9.6 KiB
Python

"""Step definitions for actor_registry_new_coverage.feature."""
from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock
from behave import given, then, when # type: ignore[import-untyped]
from behave.runner import Context # type: ignore[import-untyped]
from cleveragents.actor.config import ActorConfiguration
from cleveragents.actor.registry import ActorRegistry
from cleveragents.core.exceptions import ValidationError
from cleveragents.domain.models.core import Actor
from cleveragents.providers.registry import ProviderCapabilities, ProviderInfo
def _make_actor(name: str, provider: str = "openai", model: str = "gpt-4") -> Actor:
actor = MagicMock(spec=Actor)
actor.name = name
actor.provider = provider
actor.model = model
return actor
def _make_provider_info(
name: str, model: str, provider_type_value: str = "openai"
) -> ProviderInfo:
info = MagicMock(spec=ProviderInfo)
info.name = name
info.default_model = model
type_mock = MagicMock()
type_mock.value = provider_type_value
info.provider_type = type_mock
info.capabilities = ProviderCapabilities(
supports_streaming=True,
supports_tool_calls=True,
supports_vision=False,
supports_json_mode=True,
)
return info
def _make_settings(provider: str | None = None, model: str | None = None) -> MagicMock:
settings = MagicMock()
defaults = MagicMock()
defaults.provider = provider
defaults.model = model
settings.resolve_provider_defaults.return_value = defaults
return settings
@given("a stubbed actor registry")
def step_stubbed_registry(context: Context) -> None:
context.actor_service = MagicMock()
context.provider_registry = MagicMock()
context.settings = _make_settings()
context.registry = ActorRegistry(
actor_service=context.actor_service,
provider_registry=context.provider_registry,
settings=context.settings,
)
@then("the registry should have an actor service")
def step_has_actor_service(context: Context) -> None:
assert context.registry._actor_service is context.actor_service
@then("the registry should have a provider registry")
def step_has_provider_registry(context: Context) -> None:
assert context.registry._provider_registry is context.provider_registry
@then("the registry should have settings")
def step_has_settings(context: Context) -> None:
assert context.registry._settings is context.settings
@when('I format actor name for provider "{provider}" and model "{model}"')
def step_format_name(context: Context, provider: str, model: str) -> None:
context.result = context.registry._actor_name(provider, model)
@then('the actor name should be "{expected}"')
def step_assert_actor_name(context: Context, expected: str) -> None:
assert context.result == expected
@when(
'I build a graph descriptor for provider "{provider}" model "{model}" source "{source}"'
)
def step_build_descriptor(
context: Context, provider: str, model: str, source: str
) -> None:
context.descriptor = context.registry._build_graph_descriptor(
provider=provider, model=model, source=source
)
@then("the descriptor should contain provider model and source")
def step_assert_descriptor(context: Context) -> None:
d = context.descriptor
assert d["provider"] == "openai"
assert d["model"] == "gpt-4"
assert d["source"] == "test"
@when("I build a canonical blob from a config and raw blob")
def step_build_canonical(context: Context) -> None:
config = ActorConfiguration(
provider="openai", model="gpt-4", options={"temperature": 0.7}
)
raw: dict[str, Any] = {"extra": "data"}
context.blob = context.registry._canonical_blob(
config, raw, source="custom", default_graph=None
)
@then("the blob should contain provider model and source keys")
def step_assert_canonical_blob(context: Context) -> None:
b = context.blob
assert "provider" in b
assert "model" in b
assert "source" in b
@given("a stubbed actor registry with no configured providers")
def step_registry_no_providers(context: Context) -> None:
context.actor_service = MagicMock()
context.provider_registry = MagicMock()
context.provider_registry.get_configured_providers.return_value = []
context.settings = _make_settings()
context.registry = ActorRegistry(
actor_service=context.actor_service,
provider_registry=context.provider_registry,
settings=context.settings,
)
@when("I ensure built-in actors")
def step_ensure_built_ins(context: Context) -> None:
context.result = context.registry.ensure_built_in_actors()
@then("the registry result should be an empty list")
def step_assert_empty_list(context: Context) -> None:
assert context.result == []
@given("a stubbed actor registry with configured providers")
def step_registry_with_providers(context: Context) -> None:
context.actor_service = MagicMock()
info = _make_provider_info("openai", "gpt-4")
context.provider_registry = MagicMock()
context.provider_registry.get_configured_providers.return_value = [info]
context.actor_service.get_default_actor.return_value = None
actor = _make_actor("openai/gpt-4")
context.actor_service.upsert_actor.return_value = actor
context.settings = _make_settings()
context.registry = ActorRegistry(
actor_service=context.actor_service,
provider_registry=context.provider_registry,
settings=context.settings,
)
@then("the result should contain created actors")
def step_assert_actors_created(context: Context) -> None:
assert len(context.result) > 0
@then("the default actor should be set")
def step_assert_default_set(context: Context) -> None:
context.actor_service.set_default_actor.assert_called()
@given("a stubbed actor registry with configured providers and provider defaults")
def step_registry_with_defaults(context: Context) -> None:
context.actor_service = MagicMock()
info = _make_provider_info("anthropic", "claude-3", "anthropic")
context.provider_registry = MagicMock()
context.provider_registry.get_configured_providers.return_value = [info]
context.actor_service.get_default_actor.return_value = None
actor = _make_actor("anthropic/claude-3", provider="anthropic", model="claude-3")
context.actor_service.upsert_actor.return_value = actor
context.settings = _make_settings(provider="anthropic", model="claude-3")
context.registry = ActorRegistry(
actor_service=context.actor_service,
provider_registry=context.provider_registry,
settings=context.settings,
)
@then("the preferred provider should be set as default")
def step_assert_preferred_default(context: Context) -> None:
context.actor_service.set_default_actor.assert_called()
@when('I upsert a custom actor with name "local/test" and valid config')
def step_upsert_custom(context: Context) -> None:
actor = _make_actor("local/test")
context.actor_service.upsert_actor.return_value = actor
context.result = context.registry.upsert_actor(
name="local/test",
config_blob={"provider": "openai", "model": "gpt-4"},
)
@then("the actor should be persisted via the service")
def step_assert_persisted(context: Context) -> None:
assert context.actor_service.upsert_actor.called
@when("I upsert an actor with unsafe config but no unsafe flag")
def step_upsert_unsafe_no_flag(context: Context) -> None:
context.error = None
try:
context.registry.upsert_actor(
name="local/unsafe",
config_blob={"provider": "openai", "model": "gpt-4", "unsafe": True},
)
except ValidationError as exc:
context.error = exc
@then("a ValidationError should be raised")
def step_assert_validation_error(context: Context) -> None:
assert context.error is not None, "Expected ValidationError but none raised"
assert isinstance(context.error, ValidationError)
@when('I get actor "local/test"')
def step_get_actor(context: Context) -> None:
context.registry.get_actor("local/test")
@then("the actor service get_actor should be called")
def step_assert_get_called(context: Context) -> None:
context.actor_service.get_actor.assert_called_with("local/test")
@when("I list actors")
def step_list_actors(context: Context) -> None:
context.actor_service.list_actors.return_value = []
context.registry.list_actors()
@then("the actor service list_actors should be called")
def step_assert_list_called(context: Context) -> None:
context.actor_service.list_actors.assert_called()
@when('I remove actor "local/test"')
def step_remove_actor(context: Context) -> None:
context.registry.remove_actor("local/test")
@then("the actor service remove_actor should be called")
def step_assert_remove_called(context: Context) -> None:
context.actor_service.remove_actor.assert_called_with("local/test")
@when('I set default actor "local/test"')
def step_set_default(context: Context) -> None:
context.actor_service.set_default_actor.return_value = _make_actor("local/test")
context.registry.set_default_actor("local/test")
@then("the actor service set_default_actor should be called")
def step_assert_set_default_called(context: Context) -> None:
context.actor_service.set_default_actor.assert_called_with("local/test")
@when("I get default actor")
def step_get_default(context: Context) -> None:
context.actor_service.get_default_actor.return_value = None
context.registry.get_default_actor()
@then("the actor service get_default_actor should be called")
def step_assert_get_default_called(context: Context) -> None:
context.actor_service.get_default_actor.assert_called()