File size: 4,504 Bytes
7c6ffa6 f415181 7c6ffa6 f415181 7c6ffa6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | """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)
|