| """Shared pytest fixtures. |
| |
| Generalizes the in-memory-Qdrant + local-hashing-embedder trick (previously |
| hand-wired in the root offline scripts) into reusable fixtures, and provides |
| temp-SQLite account fixtures plus HF-sync mocks — so the whole default suite |
| runs with NO credentials, network, or Docker. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import hashlib |
| import os |
| import re |
| import sqlite3 |
| import sys |
| from pathlib import Path |
|
|
| import pytest |
|
|
| |
| ROOT = Path(__file__).resolve().parents[1] |
| if str(ROOT) not in sys.path: |
| sys.path.insert(0, str(ROOT)) |
|
|
| CDMS_DB = ROOT / "data" / "cdms_metadata.db" |
| _DIM = 1536 |
| _TOKEN = re.compile(r"[a-z0-9]+") |
|
|
|
|
| |
| |
| |
| def _embed(text: str): |
| import numpy as np |
|
|
| v = np.zeros(_DIM, dtype=np.float32) |
| for tok in _TOKEN.findall((text or "").lower()): |
| idx = int(hashlib.md5(tok.encode()).hexdigest(), 16) % _DIM |
| v[idx] += 1.0 |
| n = np.linalg.norm(v) |
| if n > 0: |
| v /= n |
| return v.tolist() |
|
|
|
|
| class LocalEmbedder: |
| """Drop-in replacement for the OpenAI embedding service (offline, deterministic).""" |
|
|
| def generate_embedding(self, text: str): |
| return _embed(text) |
|
|
|
|
| @pytest.fixture(scope="session") |
| def local_embedder(): |
| return LocalEmbedder() |
|
|
|
|
| |
| |
| |
| @pytest.fixture() |
| def account_store(tmp_path): |
| from src.accounts.store import AccountStore |
|
|
| return AccountStore(tmp_path / "accounts.db") |
|
|
|
|
| @pytest.fixture() |
| def accounts_service(tmp_path, monkeypatch): |
| """Real AccountsService on a temp DB with sync OFF (no HF token) and a |
| known SESSION_SECRET so token tests are deterministic.""" |
| monkeypatch.setenv("SESSION_SECRET", "unit-test-secret") |
| monkeypatch.delenv("HF_DATA_REPO", raising=False) |
| monkeypatch.delenv("HF_DATA_TOKEN", raising=False) |
| from src.accounts.service import AccountsService |
|
|
| svc = AccountsService(db_path=str(tmp_path / "accounts.db"), daily_quota=5) |
| assert svc.sync.enabled is False |
| return svc |
|
|
|
|
| |
| |
| |
| @pytest.fixture(scope="session") |
| def in_memory_rag(): |
| """Build an in-memory Qdrant from the committed chunk DB and wire the REAL |
| CDMSRAGSearch to it with the local embedder. Skips if the index or |
| qdrant-client is unavailable.""" |
| if not CDMS_DB.exists(): |
| pytest.skip(f"CDMS index not present ({CDMS_DB}); skipping integration test") |
| try: |
| from src.rag.vector_store import QdrantVectorStore |
| from src.cdms.rag_search import CDMSRAGSearch |
| except Exception as e: |
| pytest.skip(f"RAG deps unavailable: {e}") |
|
|
| conn = sqlite3.connect(str(CDMS_DB)) |
| try: |
| rows = conn.execute( |
| "SELECT dc.id, dc.content, dc.page_number, dc.document_id, d.filename " |
| "FROM document_chunks dc JOIN documents d ON dc.document_id = d.id" |
| ).fetchall() |
| finally: |
| conn.close() |
| if not rows: |
| pytest.skip("CDMS index has no chunks") |
|
|
| store = QdrantVectorStore() |
| for cid, content, page, docid, filename in rows: |
| if not content: |
| continue |
| store.add_document_chunk( |
| str(cid), |
| _embed(content), |
| { |
| "content": content, |
| "source_file": filename, |
| "page_number": page or 0, |
| "document_id": docid, |
| }, |
| ) |
|
|
| searcher = CDMSRAGSearch() |
| searcher.vector_store = store |
| searcher.embedding_service = LocalEmbedder() |
| return searcher |
|
|