262 lines
8.4 KiB
Python
262 lines
8.4 KiB
Python
"""Production AI route contract tests.
|
|
|
|
These tests intentionally exercise the router without any network, Redis, or
|
|
paid-provider call. Provider health observations must never reorder the
|
|
executable chain or authorize a direct Gemini jump.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
from typing import Any
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
|
|
from src.services import ai_router as ai_router_module
|
|
from src.services.ai_providers.interfaces import AIResult
|
|
from src.services.ai_router import (
|
|
AIProviderEnum,
|
|
AIProviderRegistry,
|
|
AIRouter,
|
|
AIRouterExecutor,
|
|
reset_ai_router,
|
|
)
|
|
from src.services.complexity_scorer import ComplexityScore
|
|
from src.services.intent_classifier import IntentResult, IntentType
|
|
|
|
PRODUCTION_ENUM_ORDER = [
|
|
AIProviderEnum.OLLAMA_GCP_A,
|
|
AIProviderEnum.OLLAMA_GCP_B,
|
|
AIProviderEnum.OLLAMA_LOCAL,
|
|
AIProviderEnum.GEMINI,
|
|
]
|
|
PRODUCTION_NAME_ORDER = [provider.value for provider in PRODUCTION_ENUM_ORDER]
|
|
|
|
|
|
class _NoCacheRedis:
|
|
def __init__(self, *, gemini_disabled: bool = False) -> None:
|
|
self.gemini_disabled = gemini_disabled
|
|
self.set_calls: list[tuple[str, str, int | None]] = []
|
|
|
|
async def get(self, key: str) -> bytes | None:
|
|
if key == "ai:control:disabled:gemini":
|
|
return b"1" if self.gemini_disabled else b"0"
|
|
return None
|
|
|
|
async def set(self, key: str, value: str, ex: int | None = None) -> None:
|
|
self.set_calls.append((key, value, ex))
|
|
|
|
|
|
class _Provider:
|
|
is_enabled = True
|
|
capabilities = {"rca", "chat", "tool_calling", "code_review"}
|
|
|
|
def __init__(self, name: str, *, success: bool) -> None:
|
|
self.name = name
|
|
self.success = success
|
|
self.calls = 0
|
|
self.privacy_level = "cloud" if name == "gemini" else "local"
|
|
|
|
async def analyze(
|
|
self,
|
|
prompt: str,
|
|
context: dict[str, Any] | None = None,
|
|
) -> AIResult:
|
|
self.calls += 1
|
|
return AIResult(
|
|
raw_response=f"{self.name}-response" if self.success else "",
|
|
success=self.success,
|
|
provider=self.name,
|
|
error=None if self.success else "forced_failure",
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_router_singletons(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("ENABLE_GEMINI", "true")
|
|
yield
|
|
reset_ai_router()
|
|
|
|
|
|
def _router_with_failover_primary(primary: str) -> AIRouter:
|
|
router = AIRouter()
|
|
router._failover_manager = MagicMock()
|
|
router._failover_manager.select_provider = AsyncMock(
|
|
return_value=SimpleNamespace(
|
|
primary=SimpleNamespace(provider_name=primary),
|
|
fallback_chain=[],
|
|
)
|
|
)
|
|
return router
|
|
|
|
|
|
def _registry(*providers: _Provider) -> AIProviderRegistry:
|
|
registry = AIProviderRegistry()
|
|
for provider in providers:
|
|
registry.register(provider)
|
|
return registry
|
|
|
|
|
|
def test_legacy_providers_register_as_non_executable_shadow_metadata() -> None:
|
|
registry = _registry(
|
|
_Provider("claude", success=True),
|
|
_Provider("nemotron", success=True),
|
|
_Provider("openclaw_nemo", success=True),
|
|
)
|
|
|
|
assert set(registry.names()) == {"claude", "nemotron", "openclaw_nemo"}
|
|
assert registry.all_enabled() == []
|
|
assert registry.get("claude") is None
|
|
assert registry.get("nemotron") is None
|
|
assert registry.get("openclaw_nemo") is None
|
|
|
|
|
|
def test_every_risk_and_complexity_starts_on_logical_ollama() -> None:
|
|
router = _router_with_failover_primary("gemini")
|
|
|
|
for intent in IntentType:
|
|
for score in range(1, 6):
|
|
provider, _model, _reason = router._select_provider_and_model(
|
|
intent,
|
|
IntentResult(
|
|
intent=intent,
|
|
confidence=1.0,
|
|
method="test",
|
|
),
|
|
ComplexityScore(score=score),
|
|
)
|
|
assert provider == AIProviderEnum.OLLAMA
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_selected_gemini_cannot_become_router_primary() -> None:
|
|
router = _router_with_failover_primary("gemini")
|
|
|
|
decision = await router.route(
|
|
"diagnose cascading failure",
|
|
context={"intent_hint": "diagnose"},
|
|
)
|
|
|
|
assert decision.selected_provider == AIProviderEnum.OLLAMA_GCP_A
|
|
assert [provider for provider, _model in decision.fallback_chain] == (
|
|
PRODUCTION_ENUM_ORDER[1:]
|
|
)
|
|
assert decision.to_dict()["production_route_contract"] == PRODUCTION_NAME_ORDER
|
|
|
|
|
|
def test_sync_and_tool_calling_publish_the_same_exact_route() -> None:
|
|
router = _router_with_failover_primary("gemini")
|
|
|
|
sync_decision = router.route_sync(
|
|
"review code",
|
|
context={"intent_hint": "code_review"},
|
|
)
|
|
tool_provider, _tool_model, tool_fallback = router.route_tool_calling()
|
|
|
|
assert sync_decision.selected_provider == AIProviderEnum.OLLAMA_GCP_A
|
|
assert [provider for provider, _model in sync_decision.fallback_chain] == (
|
|
PRODUCTION_ENUM_ORDER[1:]
|
|
)
|
|
assert tool_provider == AIProviderEnum.OLLAMA_GCP_A
|
|
assert [provider for provider, _model in tool_fallback] == (
|
|
PRODUCTION_ENUM_ORDER[1:]
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_executor_rejects_direct_cloud_or_legacy_only_route(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
legacy = _Provider("openclaw_nemo", success=True)
|
|
gemini = _Provider("gemini", success=True)
|
|
executor = AIRouterExecutor(_registry(legacy, gemini))
|
|
monkeypatch.setattr(ai_router_module._settings, "MOCK_MODE", False)
|
|
|
|
result = await executor.execute(
|
|
prompt="bypass attempt",
|
|
provider_order=["openclaw_nemo", "gemini", "claude"],
|
|
context={"intent_hint": "diagnose"},
|
|
)
|
|
|
|
assert result.success is False
|
|
assert result.error == "production_route_missing_ollama_lane"
|
|
assert legacy.calls == 0
|
|
assert gemini.calls == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_executor_runs_exact_four_hops_in_order(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
providers = [
|
|
_Provider("ollama_gcp_a", success=False),
|
|
_Provider("ollama_gcp_b", success=False),
|
|
_Provider("ollama_local", success=False),
|
|
_Provider("gemini", success=True),
|
|
]
|
|
redis = _NoCacheRedis()
|
|
monkeypatch.setattr(ai_router_module._settings, "MOCK_MODE", False)
|
|
monkeypatch.setattr("src.core.redis_client.get_redis", lambda: redis)
|
|
|
|
result = await AIRouterExecutor(_registry(*providers)).execute(
|
|
prompt="diagnose alert",
|
|
provider_order=["ollama", "openclaw_nemo", "gemini", "claude"],
|
|
context={"intent_hint": "diagnose"},
|
|
)
|
|
|
|
assert result.success is True
|
|
assert result.provider == "gemini"
|
|
assert [provider.calls for provider in providers] == [1, 1, 1, 1]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_disabled_gemini_is_never_readded_or_called(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
providers = [
|
|
_Provider("ollama_gcp_a", success=False),
|
|
_Provider("ollama_gcp_b", success=False),
|
|
_Provider("ollama_local", success=False),
|
|
_Provider("gemini", success=True),
|
|
]
|
|
redis = _NoCacheRedis(gemini_disabled=True)
|
|
monkeypatch.setattr(ai_router_module._settings, "MOCK_MODE", False)
|
|
monkeypatch.setattr("src.core.redis_client.get_redis", lambda: redis)
|
|
|
|
result = await AIRouterExecutor(_registry(*providers)).execute(
|
|
prompt="diagnose alert",
|
|
provider_order=PRODUCTION_NAME_ORDER,
|
|
context={"intent_hint": "diagnose"},
|
|
)
|
|
|
|
assert result.success is False
|
|
assert "gemini: disabled_by_runtime_control" in str(result.error)
|
|
assert [provider.calls for provider in providers] == [1, 1, 1, 0]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_code_review_local_gate_removes_paid_fallback(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
providers = [
|
|
_Provider("ollama_gcp_a", success=False),
|
|
_Provider("ollama_gcp_b", success=False),
|
|
_Provider("ollama_local", success=True),
|
|
_Provider("gemini", success=True),
|
|
]
|
|
redis = _NoCacheRedis()
|
|
monkeypatch.setattr(ai_router_module._settings, "MOCK_MODE", False)
|
|
monkeypatch.setattr("src.core.redis_client.get_redis", lambda: redis)
|
|
|
|
result = await AIRouterExecutor(_registry(*providers)).execute(
|
|
prompt="review private code",
|
|
provider_order=PRODUCTION_NAME_ORDER,
|
|
context={"intent_hint": "code_review"},
|
|
require_local=True,
|
|
)
|
|
|
|
assert result.success is True
|
|
assert result.provider == "ollama_local"
|
|
assert providers[-1].calls == 0
|