Files
awoooi/apps/api/tests/test_ai_router_diagnose_fallback.py

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