Files
cleveragents-core/features/steps/provider_fixes_steps.py

393 lines
13 KiB
Python

"""Step definitions for provider_fixes.feature."""
from __future__ import annotations
import logging
import os
from typing import Any
from unittest.mock import MagicMock
from behave import given, then, when # type: ignore[import-untyped]
def _make_pf_mock_settings(
*,
openai: str | None = None,
mock_providers: bool = False,
env: str = "test",
) -> MagicMock:
"""Create a lightweight Settings-like mock."""
settings = MagicMock()
settings.openai_api_key = openai
settings.anthropic_api_key = None
settings.google_api_key = None
settings.gemini_api_key = None
settings.azure_api_key = None
settings.openrouter_api_key = None
settings.cohere_api_key = None
settings.groq_api_key = None
settings.together_api_key = None
settings.default_provider = None
settings.default_model = None
settings.azure_openai_endpoint = None
settings.azure_openai_api_version = None
settings.azure_openai_deployment = None
settings.openrouter_organization = None
settings.mock_providers = mock_providers
settings.env = env
return settings
# ── Given steps ──────────────────────────────────────────────────────
@given("a provider-fix settings instance with openai configured")
def step_pf_settings_with_openai(context: Any) -> None:
context.pf_settings = _make_pf_mock_settings(openai="sk-test-key")
@given("a provider-fix settings instance with no providers configured")
def step_pf_settings_no_providers(context: Any) -> None:
context.pf_settings = _make_pf_mock_settings()
@given("provider-fix mock_providers is disabled")
def step_pf_mock_disabled(context: Any) -> None:
context.pf_settings.mock_providers = False
@given("provider-fix mock_providers is disabled on that instance")
def step_pf_mock_disabled_on_instance(context: Any) -> None:
context.pf_settings.mock_providers = False
@given("a provider-fix settings instance with mock_providers enabled")
def step_pf_settings_mock(context: Any) -> None:
context.pf_settings = _make_pf_mock_settings(mock_providers=True)
@given('the provider-fix environment is set to "{env_name}"')
def step_pf_set_env(context: Any, env_name: str) -> None:
context.pf_settings.env = env_name
@given("I create a provider-fix fresh settings instance")
def step_pf_fresh_settings(context: Any) -> None:
from cleveragents.config.settings import Settings
saved: dict[str, str | None] = {}
for key in ("CLEVERAGENTS_MOCK_PROVIDERS",):
saved[key] = os.environ.pop(key, None)
try:
Settings._instance = None # type: ignore[attr-defined]
context.pf_fresh_settings = Settings()
finally:
for key, val in saved.items():
if val is not None:
os.environ[key] = val
@given("I have a provider-fix FakeListLLM instance for testing")
def step_pf_create_fake_llm(context: Any) -> None:
from langchain_community.llms import FakeListLLM
context.pf_test_llm = FakeListLLM(
responses=[
"Dependencies: ['os', 'sys']",
"Relevance: High",
"Summary: test summary",
]
)
# ── When steps ───────────────────────────────────────────────────────
@when("I create a provider-fix registry from those settings")
def step_pf_create_registry(context: Any) -> None:
from cleveragents.providers.registry import ProviderRegistry
context.pf_registry = ProviderRegistry(settings=context.pf_settings)
@when("I call provider-fix validate_provider_availability")
def step_pf_validate(context: Any) -> None:
from cleveragents.config.settings import Settings
# Clear all provider env vars so Settings sees no providers
provider_env_keys = [
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"GOOGLE_API_KEY",
"GOOGLE_GENAI_API_KEY",
"AZURE_OPENAI_API_KEY",
"AZURE_API_KEY",
"OPENROUTER_API_KEY",
"GEMINI_API_KEY",
"GOOGLE_GEMINI_API_KEY",
"HF_TOKEN",
"HUGGINGFACEHUB_API_TOKEN",
"HUGGING_FACE_HUB_TOKEN",
"COHERE_API_KEY",
"PERPLEXITY_API_KEY",
"GROQ_API_KEY",
"TOGETHER_API_KEY",
]
saved_env: dict[str, str | None] = {}
if not context.pf_settings.openai_api_key:
for key in provider_env_keys:
saved_env[key] = os.environ.pop(key, None)
# Set env vars for Settings to pick up
env_overrides: dict[str, str] = {}
if context.pf_settings.mock_providers:
env_overrides["CLEVERAGENTS_MOCK_PROVIDERS"] = "true"
if hasattr(context.pf_settings, "env"):
env_overrides["CLEVERAGENTS_ENV"] = context.pf_settings.env
for k, v in env_overrides.items():
os.environ[k] = v
try:
Settings._instance = None # type: ignore[attr-defined]
real = Settings()
if context.pf_settings.openai_api_key:
real.openai_api_key = context.pf_settings.openai_api_key
context.pf_error = None
context.pf_warning_logged = False
class _Catcher(logging.Handler):
def __init__(self) -> None:
super().__init__()
self.warnings: list[str] = []
def emit(self, record: logging.LogRecord) -> None:
if record.levelno >= logging.WARNING:
self.warnings.append(record.getMessage())
handler = _Catcher()
log = logging.getLogger("cleveragents.config.settings")
log.addHandler(handler)
try:
real.validate_provider_availability()
except ValueError as exc:
context.pf_error = exc
finally:
log.removeHandler(handler)
context.pf_warning_logged = len(handler.warnings) > 0
finally:
for key, val in saved_env.items():
if val is not None:
os.environ[key] = val
else:
os.environ.pop(key, None)
for k in env_overrides:
os.environ.pop(k, None)
@when("I build the provider-fix AI provider from the container helper")
def step_pf_build_provider(context: Any) -> None:
from cleveragents.application.container import get_ai_provider
from cleveragents.providers.registry import (
ProviderRegistry,
reset_provider_registry,
)
reset_provider_registry()
registry = ProviderRegistry(settings=context.pf_settings)
context.pf_provider_result = get_ai_provider(
settings=context.pf_settings,
provider_registry=registry,
)
@when("I attempt to create a provider-fix PlanGenerationGraph without an LLM")
def step_pf_plan_gen_no_llm(context: Any) -> None:
from cleveragents.agents.graphs.plan_generation import (
PlanGenerationGraph,
)
context.pf_error = None
try:
PlanGenerationGraph(llm=None)
except ValueError as exc:
context.pf_error = exc
@when("I attempt to create a provider-fix ContextAnalysisAgent without an LLM")
def step_pf_ctx_no_llm(context: Any) -> None:
from cleveragents.agents.graphs.context_analysis import (
ContextAnalysisAgent,
)
context.pf_error = None
try:
ContextAnalysisAgent(llm=None)
except ValueError as exc:
context.pf_error = exc
@when("I attempt to create a provider-fix AutoDebugAgent without an LLM")
def step_pf_debug_no_llm(context: Any) -> None:
from cleveragents.agents.graphs.auto_debug import AutoDebugAgent
context.pf_error = None
try:
AutoDebugAgent(llm=None)
except ValueError as exc:
context.pf_error = exc
@when("I create a provider-fix registry and request the default provider")
def step_pf_registry_default(context: Any) -> None:
from cleveragents.providers.registry import ProviderRegistry
context.pf_registry = ProviderRegistry(settings=context.pf_settings)
context.pf_default = context.pf_registry.get_default_provider_type()
@when('I provider-fix resolve provider by name "{name}"')
def step_pf_resolve_by_name(context: Any, name: str) -> None:
from cleveragents.providers.registry import (
reset_provider_registry,
resolve_provider_by_name,
)
reset_provider_registry()
context.pf_error = None
context.pf_resolved = None
try:
context.pf_resolved = resolve_provider_by_name(
name, settings=context.pf_settings
)
except ValueError as exc:
context.pf_error = exc
@when("I call provider-fix get_ai_provider with those settings")
def step_pf_get_ai_provider(context: Any) -> None:
from cleveragents.application.container import get_ai_provider
from cleveragents.providers.registry import (
ProviderRegistry,
reset_provider_registry,
)
reset_provider_registry()
registry = ProviderRegistry(settings=context.pf_settings)
context.pf_provider_result = get_ai_provider(
settings=context.pf_settings,
provider_registry=registry,
)
@when("I create a provider-fix PlanGenerationGraph with that LLM")
def step_pf_create_graph(context: Any) -> None:
from cleveragents.agents.graphs.plan_generation import (
PlanGenerationGraph,
)
context.pf_error = None
try:
context.pf_graph = PlanGenerationGraph(llm=context.pf_test_llm)
except Exception as exc:
context.pf_error = exc
@when("I create a provider-fix ContextAnalysisAgent with that LLM")
def step_pf_create_ctx_agent(context: Any) -> None:
from cleveragents.agents.graphs.context_analysis import (
ContextAnalysisAgent,
)
context.pf_error = None
try:
context.pf_agent = ContextAnalysisAgent(llm=context.pf_test_llm)
except Exception as exc:
context.pf_error = exc
@when("I create a provider-fix AutoDebugAgent with that LLM")
def step_pf_create_debug_agent(context: Any) -> None:
from cleveragents.agents.graphs.auto_debug import AutoDebugAgent
context.pf_error = None
try:
context.pf_agent = AutoDebugAgent(llm=context.pf_test_llm)
except Exception as exc:
context.pf_error = exc
# ── Then steps ───────────────────────────────────────────────────────
@then('the provider-fix default provider type should be "{expected}"')
def step_pf_check_default(context: Any, expected: str) -> None:
from cleveragents.providers.registry import ProviderType
default = context.pf_registry.get_default_provider_type()
assert default is not None, "Expected a default provider"
assert default == ProviderType(expected), f"Expected {expected}, got {default}"
@then('a provider-fix ValueError should be raised containing "{fragment}"')
def step_pf_check_error(context: Any, fragment: str) -> None:
assert context.pf_error is not None, (
f"Expected ValueError with '{fragment}' but no error"
)
assert fragment in str(context.pf_error), (
f"Expected '{fragment}' in: {context.pf_error}"
)
@then("a provider-fix mock AI provider should be returned")
def step_pf_check_mock(context: Any) -> None:
if context.pf_provider_result is not None:
name = getattr(context.pf_provider_result, "_name", "")
cls = type(context.pf_provider_result).__name__.lower()
assert "mock" in name.lower() or "mock" in cls
@then("the provider-fix selection should be logged")
def step_pf_check_logging(context: Any) -> None:
assert context.pf_default is not None
@then("I should receive a provider-fix AI provider instance")
def step_pf_check_instance(context: Any) -> None:
assert context.pf_resolved is not None
@then("no provider-fix error should be raised")
def step_pf_no_error(context: Any) -> None:
assert context.pf_error is None, f"Expected no error but got: {context.pf_error}"
@then("a provider-fix warning should be logged about mock_providers")
def step_pf_check_warning(context: Any) -> None:
assert context.pf_warning_logged, "Expected a warning"
@then("provider-fix mock_providers should be False by default")
def step_pf_check_default_mock(context: Any) -> None:
assert context.pf_fresh_settings.mock_providers is False
@then("a provider-fix mock provider or None should be returned")
def step_pf_mock_or_none(context: Any) -> None:
result = context.pf_provider_result
if result is not None:
cls = type(result).__name__.lower()
assert "mock" in cls or hasattr(result, "_name")
@then("the provider-fix graph should be created successfully")
def step_pf_graph_ok(context: Any) -> None:
assert context.pf_error is None, f"Expected no error but got: {context.pf_error}"
assert context.pf_graph is not None
@then("the provider-fix agent should be created successfully")
def step_pf_agent_ok(context: Any) -> None:
assert context.pf_error is None, f"Expected no error but got: {context.pf_error}"
assert context.pf_agent is not None