Spaces:
Sleeping
Sleeping
File size: 7,230 Bytes
e86dfae | 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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | """
Test Fixtures and Configuration
Shared pytest fixtures for unit and integration tests.
"""
import asyncio
import uuid
from typing import AsyncGenerator, Generator
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import pytest_asyncio
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
# ── Test database (SQLite in-memory for unit tests) ────────────────────────────
SQLITE_URL = "sqlite:///./test.db"
# We need to patch settings BEFORE importing app modules
import os
os.environ.setdefault("DATABASE_URL", SQLITE_URL)
os.environ.setdefault("GEMINI_API_KEY", "test-api-key-000000000000000000000000")
os.environ.setdefault("CLERK_JWKS_URL", "https://test.clerk.com/.well-known/jwks.json")
os.environ.setdefault("REDIS_URL", "redis://localhost:6379/0")
from app.models import ( # noqa: F401 — register tables
audit,
chat,
document,
prompt,
user,
)
from app.models.base import Base
# ── Database fixtures ─────────────────────────────────────────────────────────
@pytest.fixture(scope="session")
def test_engine():
"""Create SQLite test engine (session-scoped for performance)."""
engine = create_engine(
SQLITE_URL,
connect_args={"check_same_thread": False},
)
Base.metadata.create_all(bind=engine)
yield engine
Base.metadata.drop_all(bind=engine)
@pytest.fixture(scope="function")
def db_session(test_engine) -> Generator[Session, None, None]:
"""
Provide a clean database session for each test.
Rolls back all changes after each test for isolation.
"""
connection = test_engine.connect()
transaction = connection.begin()
TestingSessionLocal = sessionmaker(
bind=connection, autocommit=False, autoflush=False
)
session = TestingSessionLocal()
yield session
session.close()
transaction.rollback()
connection.close()
# ── Test client fixtures ───────────────────────────────────────────────────────
@pytest.fixture(scope="function")
def client(db_session: Session):
"""
FastAPI test client with auth mocked and DB injected.
"""
from app.api.deps import get_current_user_id
from app.db.session import get_db
from app.main import app
def override_get_db():
yield db_session
def override_get_user_id():
return "test_user_001"
app.dependency_overrides[get_db] = override_get_db
app.dependency_overrides[get_current_user_id] = override_get_user_id
with TestClient(app) as c:
yield c
app.dependency_overrides.clear()
# ── Model factory fixtures ─────────────────────────────────────────────────────
@pytest.fixture
def sample_user_id() -> str:
return "test_user_001"
@pytest.fixture
def sample_document(db_session: Session, sample_user_id: str):
"""Create a sample document for tests."""
from app.models.document import Document, DocumentCategory, DocumentStatus
doc = Document(
user_id=sample_user_id,
title="Test Mining Safety Protocol",
file_name="mining_safety.pdf",
file_size=1024 * 100, # 100KB
file_type="application/pdf",
file_url="https://example.com/mining_safety.pdf",
status=DocumentStatus.COMPLETED,
category=DocumentCategory.SAFETY_PROTOCOL,
content="This document covers underground coal mine safety procedures...",
total_pages=25,
summary="A comprehensive guide to mining safety.",
key_points=["Wear PPE", "Check ventilation", "Follow evacuation plan"],
safety_score=82.5,
classification_confidence=0.95,
)
db_session.add(doc)
db_session.commit()
db_session.refresh(doc)
return doc
@pytest.fixture
def sample_embedding(db_session: Session, sample_document):
"""Create a sample document embedding for RAG tests."""
from app.models.document import DocumentEmbedding
embedding = DocumentEmbedding(
document_id=sample_document.id,
chunk_index=0,
chunk_text="Underground coal mines require adequate ventilation to prevent methane buildup.",
embedding=[0.1] * 768, # Mock 768-dim vector
page_numbers=[12, 13],
section_title="Ventilation Requirements",
start_page=12,
)
db_session.add(embedding)
db_session.commit()
db_session.refresh(embedding)
return embedding
@pytest.fixture
def sample_chat_session(db_session: Session, sample_user_id: str):
"""Create a sample chat session."""
from app.models.chat import ChatSession
session = ChatSession(
user_id=sample_user_id,
title="Test Chat Session",
document_context=[],
)
db_session.add(session)
db_session.commit()
db_session.refresh(session)
return session
# ── Gemini mock fixtures ───────────────────────────────────────────────────────
@pytest.fixture
def mock_gemini_response():
"""Mock Gemini generate_content response."""
mock = MagicMock()
mock.text = '{"category": "safety_protocol", "confidence": 0.92, "reasoning": "Contains PPE requirements", "subcategory": "underground"}'
return mock
@pytest.fixture
def mock_gemini_embedding():
"""Mock Gemini embedding response."""
return {"embedding": [0.1] * 768}
@pytest.fixture
def mock_chat_response():
"""Mock chat generation response."""
mock = MagicMock()
mock.text = (
"According to [mining_safety.pdf, Page 12], the ventilation requirements "
"state that all underground coal mines must maintain methane levels below 1%."
)
return mock
# ── Sample text fixtures ───────────────────────────────────────────────────────
SAMPLE_PDF_TEXT = """
UNDERGROUND COAL MINE SAFETY PROTOCOL
1. VENTILATION REQUIREMENTS
All underground coal mines must maintain adequate ventilation to prevent the
accumulation of hazardous gases including methane (CH4), carbon monoxide (CO),
and hydrogen sulfide (H2S).
Minimum air velocity requirements per 30 CFR 75.321:
- Main intake airways: minimum 60 feet per minute
- Working sections: minimum 60,000 cubic feet per minute
2. PERSONAL PROTECTIVE EQUIPMENT
All personnel entering underground areas must wear:
- Approved hard hat with headlamp
- Self-rescuer device
- Safety boots with steel toe and anti-static properties
3. EMERGENCY EVACUATION
Evacuation routes must be clearly marked and tested quarterly.
Emergency drills must be conducted at least twice per year per MSHA regulation.
"""
@pytest.fixture
def sample_mining_text():
return SAMPLE_PDF_TEXT
|