"""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}" )