Files
temp/features/steps/provider_registry_coverage_boost_steps.py
freemo a808c395f9 test(coverage): add Behave BDD tests to improve unit test coverage across 53 source modules
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
2026-03-09 13:01:58 -04:00

259 lines
9.0 KiB
Python

"""Step definitions for provider_registry_coverage_boost.feature.
These steps target specific uncovered lines in
src/cleveragents/providers/registry.py identified from build/coverage.xml.
"""
from __future__ import annotations
import os
import sys
import types
from typing import Any
from unittest.mock import MagicMock
from behave import given, then, when # type: ignore[import-untyped]
from cleveragents.providers.registry import (
ProviderRegistry,
ProviderType,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_boost_settings(
openai: str | None = None,
anthropic: str | None = None,
google: str | None = None,
gemini: str | None = None,
azure: str | None = None,
openrouter: str | None = None,
cohere: str | None = None,
groq: str | None = None,
together: str | None = None,
default_provider: str | None = None,
default_model: str | None = None,
azure_endpoint: str | None = None,
azure_api_version: str | None = None,
azure_deployment: str | None = None,
openrouter_org: str | None = None,
) -> MagicMock:
"""Return a fake Settings-like object populated with API keys."""
settings = MagicMock()
settings.openai_api_key = openai
settings.anthropic_api_key = anthropic
settings.google_api_key = google
settings.gemini_api_key = gemini
settings.azure_api_key = azure
settings.openrouter_api_key = openrouter
settings.cohere_api_key = cohere
settings.groq_api_key = groq
settings.together_api_key = together
settings.default_provider = default_provider
settings.default_model = default_model
if azure and not azure_endpoint:
azure_endpoint = "https://example.openai.azure.com"
settings.azure_openai_endpoint = azure_endpoint
settings.azure_openai_api_version = azure_api_version
settings.azure_openai_deployment = azure_deployment
settings.openrouter_organization = openrouter_org
return settings
def _stub_azure_langchain(context: Any) -> None:
"""Stub the langchain_openai module so AzureChatOpenAI is a recorder."""
original = sys.modules.get("langchain_openai")
calls: list[dict[str, Any]] = []
class FakeAzureChatOpenAI:
def __init__(self, **kwargs: Any) -> None:
calls.append(kwargs)
# Also provide ChatOpenAI so other imports from the module don't break.
class FakeChatOpenAI:
def __init__(self, **kwargs: Any) -> None:
pass
stub = types.ModuleType("langchain_openai")
stub.AzureChatOpenAI = FakeAzureChatOpenAI
stub.ChatOpenAI = FakeChatOpenAI
sys.modules["langchain_openai"] = stub
context._boost_azure_calls = calls
context._boost_azure_cls = FakeAzureChatOpenAI
if not hasattr(context, "_boost_cleanup"):
context._boost_cleanup = []
def _restore() -> None:
if original is None:
sys.modules.pop("langchain_openai", None)
else:
sys.modules["langchain_openai"] = original
context._boost_cleanup.append(_restore)
# ---------------------------------------------------------------------------
# Given
# ---------------------------------------------------------------------------
@given(
'a registry where settings default_provider is "{default_prov}" but only anthropic key is set'
)
def step_boost_registry_settings_default_unconfigured(
context: Any, default_prov: str
) -> None:
"""Create a registry whose settings.default_provider points to a valid but
unconfigured provider, while another provider IS configured."""
context.boost_registry = ProviderRegistry(
settings=_make_boost_settings(
anthropic="sk-ant-test",
default_provider=default_prov,
)
)
@given("CLEVERAGENTS_DEFAULT_PROVIDER env var is cleared")
def step_boost_clear_default_provider_env(context: Any) -> None:
context._boost_orig_provider_env = os.environ.get("CLEVERAGENTS_DEFAULT_PROVIDER")
os.environ.pop("CLEVERAGENTS_DEFAULT_PROVIDER", None)
@given('a registry with azure API key and deployment setting "{deployment}"')
def step_boost_azure_registry_with_deployment(context: Any, deployment: str) -> None:
context.boost_registry = ProviderRegistry(
settings=_make_boost_settings(
azure="sk-azure-test",
azure_deployment=deployment,
)
)
@given("a registry with azure API key and no deployment setting")
def step_boost_azure_registry_no_deployment(context: Any) -> None:
context.boost_registry = ProviderRegistry(
settings=_make_boost_settings(
azure="sk-azure-test",
azure_deployment=None,
)
)
@given("the Azure LangChain client is stubbed for boost")
def step_boost_stub_azure(context: Any) -> None:
_stub_azure_langchain(context)
@given("a registry with google API key configured for boost")
def step_boost_google_registry(context: Any) -> None:
context.boost_registry = ProviderRegistry(
settings=_make_boost_settings(google="sk-google-test")
)
@given("a registry with no API keys for boost")
def step_boost_no_keys_registry(context: Any) -> None:
context.boost_registry = ProviderRegistry(settings=_make_boost_settings())
@given("a registry with openrouter API key configured for boost")
def step_boost_openrouter_registry(context: Any) -> None:
context.boost_registry = ProviderRegistry(
settings=_make_boost_settings(openrouter="sk-or-test")
)
# ---------------------------------------------------------------------------
# When
# ---------------------------------------------------------------------------
@when("I request the default provider type from the boost registry")
def step_boost_get_default_provider_type(context: Any) -> None:
context.boost_result = context.boost_registry.get_default_provider_type()
@when("I call _create_provider_llm for AZURE with model_id None via boost")
def step_boost_create_azure_llm_no_model(context: Any) -> None:
context.boost_result = context.boost_registry._create_provider_llm(
ProviderType.AZURE, None
)
@when("I create an AI provider for google with empty string model_id via boost")
def step_boost_google_ai_provider_empty_model(context: Any) -> None:
# Ensure env vars don't interfere
orig_model = os.environ.pop("CLEVERAGENTS_DEFAULT_MODEL", None)
context._boost_orig_model_env = orig_model
context.boost_registry._settings.default_model = None
context.boost_ai_provider = context.boost_registry.create_ai_provider(
provider_type="google", model_id=""
)
@when("I try to create an AI provider for openrouter via boost")
def step_boost_try_openrouter_ai_provider(context: Any) -> None:
try:
context.boost_registry.create_ai_provider(provider_type="openrouter")
context.boost_error = None
except ValueError as exc:
context.boost_error = exc
@when("I create an AI provider for openrouter with empty string model_id via boost")
def step_boost_openrouter_ai_provider_empty_model(context: Any) -> None:
orig_model = os.environ.pop("CLEVERAGENTS_DEFAULT_MODEL", None)
context._boost_orig_model_env = orig_model
context.boost_registry._settings.default_model = None
context.boost_ai_provider = context.boost_registry.create_ai_provider(
provider_type="openrouter", model_id=""
)
# ---------------------------------------------------------------------------
# Then
# ---------------------------------------------------------------------------
@then("the boost result should be ProviderType ANTHROPIC")
def step_boost_result_is_anthropic(context: Any) -> None:
assert context.boost_result == ProviderType.ANTHROPIC, (
f"Expected ProviderType.ANTHROPIC, got {context.boost_result!r}"
)
@then('the stubbed Azure client deployment_name should be "{expected}"')
def step_boost_azure_deployment_name(context: Any, expected: str) -> None:
calls = context._boost_azure_calls
assert calls, "Expected AzureChatOpenAI to be instantiated"
actual = calls[-1].get("deployment_name")
assert actual == expected, f"Expected deployment_name={expected!r}, got {actual!r}"
@then('the boost AI provider model_id should be "{expected}"')
def step_boost_ai_provider_model(context: Any, expected: str) -> None:
assert context.boost_ai_provider.model_id == expected, (
f"Expected model_id={expected!r}, got {context.boost_ai_provider.model_id!r}"
)
@then("a boost ValueError should be raised")
def step_boost_value_error(context: Any) -> None:
assert context.boost_error is not None, "Expected a ValueError to be raised"
assert isinstance(context.boost_error, ValueError), (
f"Expected ValueError, got {type(context.boost_error).__name__}"
)
@then('the boost error message should contain "{fragment}"')
def step_boost_error_contains(context: Any, fragment: str) -> None:
message = str(context.boost_error).lower()
assert fragment.lower() in message, (
f"Expected error to contain {fragment!r}, got: {message!r}"
)