Spaces:
Runtime error
Runtime error
File size: 5,948 Bytes
dc1b199 32c4506 dc1b199 32c4506 dc1b199 732b14f c893230 dc1b199 32c4506 dc1b199 32c4506 dc1b199 32c4506 d611f45 dc1b199 32c4506 d611f45 32c4506 dc1b199 | 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 | """Shared pytest fixtures for all test modules.
All LLM and embedding calls are mocked. Isolation tests use a LangChain FAISS
index rooted in a per-invocation temporary directory (no shared disk state).
"""
import os
import tempfile
import threading
from collections.abc import AsyncGenerator, Generator
from pathlib import Path
from typing import Any
import pytest
import pytest_asyncio
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
# ββ Force mock/test settings before importing anything from app βββββββββββββββ
os.environ.setdefault("OPENAI_API_KEY", "")
os.environ.setdefault("DEV_MODE", "true")
# Override developer .env so tests never require a live Qdrant instance.
os.environ["VECTORSTORE_BACKEND"] = "faiss"
os.environ["ENABLE_ASYNC_PIPELINE"] = "false"
os.environ["ENABLE_SPECULATIVE_EXECUTOR"] = "false"
os.environ["ENABLE_PROMPT_CACHING"] = "false"
os.environ["ENABLE_TEMPORAL_WORKFLOW"] = "false"
os.environ["SEMANTIC_CACHE_ENABLED"] = "false"
os.environ["ENABLE_JOB_QUEUE"] = "false"
os.environ["REDIS_URL"] = ""
os.environ["ENABLE_PARALLEL_SECTION_GENERATION"] = "false"
os.environ["ALLOW_SQLITE_PARALLEL_SECTIONS"] = "false"
# These must be imported after env vars are set
from app.db.database import Base, get_db # noqa: E402
from app.embeddings.mock_client import MockEmbeddingClient # noqa: E402
from app.main import create_app # noqa: E402
from app.models.schemas import Chunk # noqa: E402
from app.vectorstore.factory import reset_vectorstore # noqa: E402
# ββ In-memory SQLite test database ββββββββββββββββββββββββββββββββββββββββββββ
@pytest.fixture(scope="session")
def event_loop_policy() -> None:
"""Use the default asyncio event loop policy."""
return None
@pytest_asyncio.fixture
async def test_db() -> AsyncGenerator[AsyncSession, None]:
"""Provide an isolated in-memory SQLite database session for each test."""
engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
factory = async_sessionmaker(engine, expire_on_commit=False)
async with factory() as session:
yield session
await engine.dispose()
# ββ Ephemeral FAISS vector store ββββββββββββββββββββββββββββββββββββββββββββββ
@pytest.fixture(autouse=True)
def reset_vs() -> Generator[None, None, None]:
"""Reset the vector store singleton before each test."""
reset_vectorstore()
yield
reset_vectorstore()
@pytest.fixture
def in_memory_vs(tmp_path: Path) -> Any:
"""Return a FAISSVectorStore with an empty index under ``tmp_path``."""
from langchain_community.embeddings import FakeEmbeddings
from app.vectorstore.faiss_wrapper import FAISSVectorStore
embedding = FakeEmbeddings(size=384)
vs = FAISSVectorStore.__new__(FAISSVectorStore)
vs._embedding = embedding
vs._index_path = tmp_path / "faiss_test"
vs._index_path.mkdir(parents=True, exist_ok=True)
vs._lock = threading.RLock()
vs._store = None
return vs
# ββ Mock embedding client βββββββββββββββββββββββββββββββββββββββββββββββββββββ
@pytest.fixture
def mock_embedder() -> MockEmbeddingClient:
"""Return the deterministic mock embedding client."""
return MockEmbeddingClient()
# ββ Sample chunks βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@pytest.fixture
def sample_chunks_tenant_a() -> list[Chunk]:
"""FIXTURE β sample chunks belonging to tenant_A."""
return [
Chunk(
chunk_id=f"ca-{i}",
doc_id="doc-a",
tenant_id="tenant_A",
text=f"FIXTURE tenant_A document chunk number {i}.",
section_type="paragraph",
token_count=10,
)
for i in range(3)
]
@pytest.fixture
def sample_chunks_tenant_b() -> list[Chunk]:
"""FIXTURE β sample chunks belonging to tenant_B."""
return [
Chunk(
chunk_id=f"cb-{i}",
doc_id="doc-b",
tenant_id="tenant_B",
text=f"FIXTURE tenant_B document chunk number {i}.",
section_type="paragraph",
token_count=10,
)
for i in range(3)
]
# ββ Temp directory ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@pytest.fixture
def tmp_dir() -> Generator[Path, None, None]:
"""Provide a temporary directory that is removed after the test."""
with tempfile.TemporaryDirectory() as d:
yield Path(d)
# ββ FastAPI test client βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@pytest_asyncio.fixture
async def async_client(test_db: AsyncSession) -> AsyncGenerator[AsyncClient, None]:
"""Provide an async HTTPX client pointed at the test FastAPI app.
The database dependency is overridden with the in-memory test session.
"""
app = create_app()
async def _override_db() -> AsyncGenerator[AsyncSession, None]:
yield test_db
app.dependency_overrides[get_db] = _override_db
async with AsyncClient(
transport=ASGITransport(app=app),
base_url="http://test",
headers={"X-Tenant-ID": "tenant_test"},
) as client:
yield client
|