forked from HAL9000/cleveragents-core
a808c395f9
Add 53 new .feature files and corresponding step definition files targeting uncovered lines identified in build/coverage.xml. Fix AmbiguousStep conflicts in 7 pre-existing step files by disambiguating step text. New tests cover: ACP clients/facade, actor CLI/config, application container, ACMS service/strategies, async worker, automation profile CLI, autonomy guardrail, bridge, change model, config CLI/service, context service, cross-plan correction, database models, decision service, decomposition clustering/service, discovery handler, langchain chat provider, langgraph nodes, materializers, multi-project service, plan apply/CLI/lifecycle/model/ preflight/resume/service, PostgreSQL analyzer, project CLI/context CLI, provider registry, reactive application/route, repositories, resolver handler, resource registry service, resume model, retry patterns, sandbox protocol, server CLI, skill CLI/service, skills registry, subplan execution/service, system CLI, UKO loader, UoW, and YAML template engine. Closes #645
283 lines
10 KiB
Python
283 lines
10 KiB
Python
"""Step definitions for plan_service_coverage_boost.feature.
|
|
|
|
Targets uncovered lines in plan_service.py to increase branch and line coverage:
|
|
- _build_actor_context: initial_context from config_blob dict
|
|
- _build_actor_context: graph_descriptor from actor dict
|
|
- _build_actor_context: context_variables fallback
|
|
- _build_actor_context: graph_descriptor from config_blob fallback
|
|
- _resolve_ai_provider_for_actor: empty provider raises PlanError
|
|
- _strip_code_fences: markdown python and generic fences
|
|
- _build_langsmith_config: returns {} when builder returns None
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import warnings
|
|
from datetime import datetime
|
|
from typing import Any
|
|
from unittest.mock import MagicMock
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.application.services.plan_service import PlanService
|
|
from cleveragents.config.settings import Settings
|
|
from cleveragents.core.exceptions import PlanError
|
|
from cleveragents.domain.models.core import Actor
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_settings_mock() -> MagicMock:
|
|
settings = MagicMock(spec=Settings)
|
|
settings.is_langsmith_enabled = False
|
|
settings.langsmith_tags = []
|
|
settings.database_url = "sqlite://:memory:"
|
|
settings.provider_configuration_diagnostics.return_value = {}
|
|
settings.configured_provider_names.return_value = []
|
|
settings.provider_expected_env_vars.return_value = []
|
|
return settings
|
|
|
|
|
|
def _make_actor(
|
|
name: str = "local/test-actor",
|
|
provider: str = "mock",
|
|
model: str = "mock-model",
|
|
config_blob: dict[str, Any] | None = None,
|
|
graph_descriptor: dict[str, Any] | None = None,
|
|
) -> Actor:
|
|
return Actor(
|
|
name=name,
|
|
provider=provider,
|
|
model=model,
|
|
config_blob=config_blob or {},
|
|
config_hash="abcd1234",
|
|
graph_descriptor=graph_descriptor,
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
|
|
|
|
def _make_actor_no_provider(name: str = "local/no-provider") -> Actor:
|
|
"""Create an Actor with empty provider/model using model_construct."""
|
|
return Actor.model_construct(
|
|
name=name,
|
|
provider="",
|
|
model="",
|
|
config_blob={},
|
|
config_hash="abcd1234",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
unsafe=False,
|
|
is_default=False,
|
|
is_built_in=False,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Background / Given
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("I have a lightweight plan service for coverage boost testing")
|
|
def step_lightweight_plan_service(context: Context) -> None:
|
|
settings = _make_settings_mock()
|
|
uow = MagicMock()
|
|
actor_service = MagicMock()
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("ignore", DeprecationWarning)
|
|
context.plan_svc = PlanService(
|
|
settings=settings,
|
|
unit_of_work=uow,
|
|
actor_service=actor_service,
|
|
)
|
|
context.plan_svc_settings = settings
|
|
context.plan_svc_actor_service = actor_service
|
|
context.plan_svc_actor = None
|
|
context.plan_svc_actor_ctx = None
|
|
context.plan_svc_error = None
|
|
context.plan_svc_stripped = None
|
|
context.plan_svc_langsmith_result = None
|
|
|
|
|
|
@given("I have an actor with initial_context dict in config_blob")
|
|
def step_actor_with_initial_context(context: Context) -> None:
|
|
context.plan_svc_actor = _make_actor(
|
|
config_blob={"initial_context": {"key1": "val1", "key2": "val2"}},
|
|
)
|
|
|
|
|
|
@given("I have an actor with a dict graph_descriptor")
|
|
def step_actor_with_graph_descriptor(context: Context) -> None:
|
|
context.plan_svc_actor = _make_actor(
|
|
graph_descriptor={"type": "langgraph", "entry": "main"},
|
|
)
|
|
|
|
|
|
@given("the actor lookup returns an actor with empty provider")
|
|
def step_actor_with_empty_provider(context: Context) -> None:
|
|
actor = _make_actor_no_provider()
|
|
context.plan_svc_actor_service.get_actor.return_value = actor
|
|
context.plan_svc_actor = actor
|
|
|
|
|
|
@given("LangSmith is enabled but build_langsmith_config returns None")
|
|
def step_langsmith_enabled_returns_none(context: Context) -> None:
|
|
context.plan_svc_settings.is_langsmith_enabled = True
|
|
context.plan_svc_settings.build_langsmith_config.return_value = None
|
|
|
|
|
|
@given("I have an actor with context_variables fallback in config_blob")
|
|
def step_actor_with_context_variables(context: Context) -> None:
|
|
context.plan_svc_actor = _make_actor(
|
|
config_blob={"context_variables": {"env": "production", "debug": "true"}},
|
|
)
|
|
|
|
|
|
@given("I have an actor with graph_descriptor in config_blob but not on the actor")
|
|
def step_actor_graph_descriptor_in_config_blob(context: Context) -> None:
|
|
context.plan_svc_actor = _make_actor(
|
|
config_blob={"graph_descriptor": {"type": "custom", "nodes": []}},
|
|
graph_descriptor=None,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I build the actor context for the actor with initial_context")
|
|
def step_build_actor_context_initial(context: Context) -> None:
|
|
context.plan_svc_actor_ctx = context.plan_svc._build_actor_context(
|
|
context.plan_svc_actor
|
|
)
|
|
|
|
|
|
@when("I build the actor context for the actor with graph_descriptor")
|
|
def step_build_actor_context_graph(context: Context) -> None:
|
|
context.plan_svc_actor_ctx = context.plan_svc._build_actor_context(
|
|
context.plan_svc_actor
|
|
)
|
|
|
|
|
|
@when("I try to resolve the AI provider for the providerless actor")
|
|
def step_resolve_provider_empty(context: Context) -> None:
|
|
try:
|
|
context.plan_svc._resolve_ai_provider_for_actor("local/no-provider")
|
|
context.plan_svc_error = None
|
|
except PlanError as exc:
|
|
context.plan_svc_error = exc
|
|
|
|
|
|
@when("I strip code fences from python-fenced content")
|
|
def step_strip_python_fences(context: Context) -> None:
|
|
content = '```python\nprint("hello")\n```'
|
|
context.plan_svc_stripped = context.plan_svc._strip_code_fences(content)
|
|
|
|
|
|
@when("I strip code fences from generic-fenced content")
|
|
def step_strip_generic_fences(context: Context) -> None:
|
|
content = "```\nsome text\n```"
|
|
context.plan_svc_stripped = context.plan_svc._strip_code_fences(content)
|
|
|
|
|
|
@when("I build the LangSmith config for a project")
|
|
def step_build_langsmith_config(context: Context) -> None:
|
|
project = MagicMock()
|
|
project.id = 1
|
|
project.name = "test-project"
|
|
context.plan_svc_langsmith_result = context.plan_svc._build_langsmith_config(
|
|
project, None, run_name="test"
|
|
)
|
|
|
|
|
|
@when("I build the actor context for the actor with context_variables")
|
|
def step_build_actor_context_context_variables(context: Context) -> None:
|
|
context.plan_svc_actor_ctx = context.plan_svc._build_actor_context(
|
|
context.plan_svc_actor
|
|
)
|
|
|
|
|
|
@when("I build the actor context for the config_blob graph_descriptor actor")
|
|
def step_build_actor_context_config_blob_graph(context: Context) -> None:
|
|
context.plan_svc_actor_ctx = context.plan_svc._build_actor_context(
|
|
context.plan_svc_actor
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("the actor invocation context should contain the initial_context entries")
|
|
def step_assert_initial_context(context: Context) -> None:
|
|
ctx = context.plan_svc_actor_ctx
|
|
assert ctx is not None, "No actor context was built"
|
|
assert ctx.initial_context == {"key1": "val1", "key2": "val2"}, (
|
|
f"Expected initial_context with key1/key2, got {ctx.initial_context}"
|
|
)
|
|
|
|
|
|
@then("the actor invocation context should contain the graph_descriptor")
|
|
def step_assert_graph_descriptor(context: Context) -> None:
|
|
ctx = context.plan_svc_actor_ctx
|
|
assert ctx is not None, "No actor context was built"
|
|
assert ctx.graph_descriptor == {"type": "langgraph", "entry": "main"}, (
|
|
f"Expected graph_descriptor, got {ctx.graph_descriptor}"
|
|
)
|
|
|
|
|
|
@then(
|
|
"the providerless actor PlanError should contain "
|
|
'"Actor is missing provider configuration"'
|
|
)
|
|
def step_assert_provider_error(context: Context) -> None:
|
|
err = context.plan_svc_error
|
|
assert err is not None, "Expected PlanError but no error was raised"
|
|
assert isinstance(err, PlanError), f"Expected PlanError, got {type(err).__name__}"
|
|
assert "Actor is missing provider configuration" in err.message, (
|
|
f"Expected 'Actor is missing provider configuration' in '{err.message}'"
|
|
)
|
|
|
|
|
|
@then("the stripped content should contain only the inner code")
|
|
def step_assert_stripped_python(context: Context) -> None:
|
|
assert context.plan_svc_stripped == 'print("hello")', (
|
|
f"Expected inner code, got {context.plan_svc_stripped!r}"
|
|
)
|
|
|
|
|
|
@then("the stripped content should contain only the inner text")
|
|
def step_assert_stripped_generic(context: Context) -> None:
|
|
assert context.plan_svc_stripped == "some text", (
|
|
f"Expected inner text, got {context.plan_svc_stripped!r}"
|
|
)
|
|
|
|
|
|
@then("the plan service LangSmith config result should be an empty dict")
|
|
def step_assert_langsmith_empty(context: Context) -> None:
|
|
result = context.plan_svc_langsmith_result
|
|
assert result == {}, f"Expected empty dict, got {result}"
|
|
|
|
|
|
@then("the actor invocation context should contain the context_variables entries")
|
|
def step_assert_context_variables(context: Context) -> None:
|
|
ctx = context.plan_svc_actor_ctx
|
|
assert ctx is not None, "No actor context was built"
|
|
assert ctx.initial_context == {"env": "production", "debug": "true"}, (
|
|
f"Expected context_variables as initial_context, got {ctx.initial_context}"
|
|
)
|
|
|
|
|
|
@then("the actor invocation context should contain the config_blob graph_descriptor")
|
|
def step_assert_config_blob_graph_descriptor(context: Context) -> None:
|
|
ctx = context.plan_svc_actor_ctx
|
|
assert ctx is not None, "No actor context was built"
|
|
assert ctx.graph_descriptor == {"type": "custom", "nodes": []}, (
|
|
f"Expected config_blob graph_descriptor, got {ctx.graph_descriptor}"
|
|
)
|