DocDoeAI / tests /test_ai_provider_fallback.py
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
f415181
Raw
History Blame Contribute Delete
4.5 kB
"""Tests for AI provider fallback tracking and is_fallback signal."""
from __future__ import annotations
import os
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch
BACKEND_DIR = Path(__file__).resolve().parents[1]
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
os.environ.setdefault("AI_PROVIDER", "mock")
os.environ.setdefault("AUTH_ENABLED", "false")
from app.core.config import get_settings
get_settings.cache_clear()
from app.services.ai_provider import (
MockAIProvider,
get_ai_provider,
)
def test_mock_provider_not_fallback_when_configured_as_mock():
os.environ["AI_PROVIDER"] = "mock"
get_settings.cache_clear()
provider = get_ai_provider()
assert isinstance(provider, MockAIProvider)
assert provider.is_fallback is False
assert provider.fallback_reason is None
def test_mock_provider_is_fallback_when_openrouter_key_missing():
"""When AI_PROVIDER=openrouter but key is absent, get_ai_provider must
return a MockAIProvider with is_fallback=True.
We mock get_settings so the .env file on disk (which may have a real key)
does not interfere with the test.
"""
mock_settings = MagicMock()
mock_settings.ai_provider = "openrouter"
mock_settings.openrouter_api_key = None
with patch("app.services.ai_provider.get_settings", return_value=mock_settings):
provider = get_ai_provider()
assert isinstance(provider, MockAIProvider)
assert provider.is_fallback is True
assert "not configured" in (provider.fallback_reason or "").lower()
def test_mock_provider_model_name():
os.environ["AI_PROVIDER"] = "mock"
get_settings.cache_clear()
provider = get_ai_provider()
assert "mock" in provider.model_name.lower()
def test_health_ai_endpoint_requires_admin(client):
"""Provider diagnostics must not be exposed to anonymous callers."""
resp = client.get("/health/ai")
assert resp.status_code == 403
def test_health_ai_endpoint_returns_provider_status_for_admin(client):
"""The admin-only diagnostic still reports the configured provider."""
from app.core.admin_auth import require_admin
client.app.dependency_overrides[require_admin] = lambda: None
try:
resp = client.get("/health/ai")
finally:
client.app.dependency_overrides.pop(require_admin, None)
assert resp.status_code == 200
data = resp.json()
assert data["status"] in ("ready", "demo", "misconfigured")
assert "provider" in data
assert "can_generate" in data
def test_ask_response_has_is_fallback(client):
resp = client.post("/ask", json={"question": "What is photosynthesis?"})
assert resp.status_code == 200
data = resp.json()
assert "is_fallback" in data
assert isinstance(data["is_fallback"], bool)
assert "model_used" in data
def test_studio_notes_response_has_is_fallback(client):
resp = client.post(
"/generate/notes",
json={"topic": "Photosynthesis", "language_preference": "English"},
)
assert resp.status_code == 200
data = resp.json()
assert "is_fallback" in data
assert isinstance(data["is_fallback"], bool)
assert "model_used" in data
def test_studio_exam_answer_response_has_is_fallback(client):
resp = client.post(
"/generate/exam-answer",
json={"topic": "Photosynthesis", "language_preference": "English"},
)
assert resp.status_code == 200
data = resp.json()
assert "is_fallback" in data
assert isinstance(data["is_fallback"], bool)
def test_mock_provider_generates_without_fallback():
os.environ["AI_PROVIDER"] = "mock"
get_settings.cache_clear()
provider = get_ai_provider()
output = provider.generate_simple_explanation(
context="Photosynthesis is the process by which plants make food using sunlight.",
language="English",
)
assert provider.is_fallback is False
assert "simple_meaning" in output
def test_unknown_provider_falls_back_to_mock():
mock_settings = MagicMock()
mock_settings.ai_provider = "nonexistent-provider"
mock_settings.sarvam_api_key = None
mock_settings.openrouter_api_key = None
mock_settings.gemini_api_key = None
mock_settings.environment = "development"
mock_settings.ai_fallback_to_mock = True
with patch("app.services.ai_provider.get_settings", return_value=mock_settings):
provider = get_ai_provider()
assert isinstance(provider, MockAIProvider)