semblance/backend/tests/conftest.py
Vadym Samoilenko 3e9ccafad2 Add LLM usage tracking infrastructure (Phases A-C)
- Model renames: gpt-5.2 → gpt-5.4-2026-03-05, gemini-3-pro-preview → gemini-3.1-pro-preview; retire gpt-4.1 via alias fallback
- New: llm_usage_context.py (ContextVar-based attribution), model_pricing.py (tiered pricing + 60s cache), usage_event.py (append-only telemetry), quota.py (user/FG quota enforcement with 80% warning)
- Wire _record_usage into all 3 LLM methods; set_llm_context at every service entry point
- Fix admin_required decorator (was sync, never awaited User.find_by_id); add active_required and with_user_context decorators
- Inject user_id into ContextVar from JWT on every authenticated request
- Add DB indexes for usage_events, model_pricing, users collections
- Seed script for model pricing (gpt-5.4 single-tier, gemini-3.1 two-tier 200k threshold)
- Fix parse_json_response NameError (logger undefined at module level)
- 70 passing tests: conftest.py with sys.modules stubs, test_usage_infrastructure.py (52 tests), rewrite stale test_llm_service.py (18 tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:08:27 +01:00

53 lines
1.9 KiB
Python

"""
conftest.py — sys.modules stubs so tests run without the full Docker venv.
All heavy external packages are replaced with MagicMocks before any app.*
module is imported. Individual tests patch specific methods as needed.
"""
import os
import sys
from unittest.mock import MagicMock, AsyncMock
# ── Fake env vars required at llm_service.py module level ─────────────────────
os.environ.setdefault('GEMINI_API_KEY', 'test-key-gemini')
os.environ.setdefault('OPENAI_API_KEY', 'test-key-openai')
def _stub(*names):
"""Register MagicMocks under each name in sys.modules."""
for name in names:
if name not in sys.modules:
sys.modules[name] = MagicMock()
# ── External packages not present in system Python ────────────────────────────
_stub(
'google', 'google.genai', 'google.genai.types',
'openai', 'openai.types', 'openai.types.responses',
'motor', 'motor.motor_asyncio',
'pymongo', 'pymongo.errors',
'quart', 'quart_cors', 'hypercorn', 'werkzeug', 'werkzeug.exceptions',
'socketio',
'bcrypt', 'jwt', 'msal',
'bson', 'bson.objectid',
'pydantic',
'PIL', 'PIL.Image',
'httpx', 'requests',
'dotenv',
'llama_cloud_services',
)
# ── app.db ─────────────────────────────────────────────────────────────────────
# Any `from app.db import get_db` will capture this AsyncMock.
# Tests that need to control DB responses should patch
# `app.models.<module>.get_db` (i.e. the local binding in the module under test).
_mock_db = MagicMock()
_mock_get_db = AsyncMock(return_value=_mock_db)
_app_db_mod = MagicMock()
_app_db_mod.get_db = _mock_get_db
sys.modules['app.db'] = _app_db_mod
# Expose for tests
mock_db = _mock_db