Files
pi-qmem/gateway/tests/conftest.py
T
Matteo Benedetto 4776b4b496 refactor: split gateway and pi-qmem extension into modules
- gateway: separate config, models, state, audit, guardrail, embeddings,
  store, metrics, cleanup and routes; keep main.py as FastAPI bootstrap
- extension: split client/config, six tools, config command and rules;
  preserve jiti entrypoint and registrations
- Dockerfile copies the complete gateway module set
- tests: update monkeypatch boundaries for modular config/state
2026-08-24 16:19:19 +02:00

155 lines
5.1 KiB
Python

"""Fixtures pytest per il Memory Gateway: FakeQdrant in-memory + mock di embed.
I test non richiedono Qdrant né Ollama: il gateway viene importato con le
dipendenze reali (fastapi/pydantic/qdrant-client/httpx) ma qdrant e embed
sono sostituiti da fake deterministici.
"""
import os
import sys
from pathlib import Path
# Disabilita il push metriche nei test
os.environ["METRICS_ENABLED"] = "false"
os.environ["API_KEYS"] = "test-key"
# Guardrail disabilitato di default nei test esistenti (abilitato nei test del guardrail)
os.environ["GUARDRAIL_ENABLED"] = "false"
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import pytest # noqa: E402
import main as gateway # noqa: E402
class FakePoint:
def __init__(self, point_id, vector=None, payload=None):
self.id = point_id
self.vector = vector or {}
self.payload = payload or {}
class FakeQdrant:
"""Implementazione in-memory dei metodi Qdrant usati dal gateway."""
def __init__(self):
self.points: dict[str, FakePoint] = {}
self.collection_exists = False
self.upsert_calls = 0
self.query_score = 0.9 # score di default per query_points (configurabile nei test)
def get_collections(self):
class _C:
def __init__(self, names):
self.collections = [type("X", (), {"name": n})() for n in names]
return _C(["memories"] if self.collection_exists else [])
def create_collection(self, **kw):
self.collection_exists = True
def create_payload_index(self, **kw):
pass
def create_vector_name(self, *a, **kw):
pass
def upsert(self, collection_name, points, **kw):
self.upsert_calls += 1
for p in points:
self.points[p.id] = FakePoint(p.id, p.vector, p.payload)
def retrieve(self, collection_name, ids, with_payload=True):
return [self.points[i] for i in ids if i in self.points]
def set_payload(self, collection_name, payload, points, **kw):
for pid in points:
if pid in self.points:
self.points[pid].payload.update(payload)
def delete(self, collection_name, points_selector, **kw):
for pid in points_selector:
self.points.pop(pid, None)
def _matches(self, pl, query_filter):
"""Applica i filtri metadata (FieldCondition match / IsEmptyCondition)."""
if not query_filter or not query_filter.must:
return True
for cond in query_filter.must:
if hasattr(cond, "key") and hasattr(cond, "match"):
if pl.get(cond.key) != cond.match.value:
return False
elif hasattr(cond, "is_empty"):
if pl.get(cond.is_empty.key):
return False
return True
def scroll(self, collection_name, scroll_filter=None, limit=None, with_payload=True, **kw):
points = [p for p in self.points.values() if self._matches(p.payload, scroll_filter)]
if limit:
points = points[:limit]
return points, None
def get_collection(self, collection_name):
class _Info:
points_count = len(self.points)
return _Info()
def query_points(self, collection_name, query=None, query_filter=None, limit=5,
score_threshold=None, with_payload=True, prefetch=None, **kw):
# Ritorna tutti i punti (score fisso); i filtri metadata sono applicati
# in modo semplice per testare kind/project_id/scope/superseded.
results = []
for p in self.points.values():
pl = p.payload
if query_filter and query_filter.must:
ok = True
for cond in query_filter.must:
if hasattr(cond, "key") and hasattr(cond, "match"):
if pl.get(cond.key) != cond.match.value:
ok = False
elif hasattr(cond, "is_empty"):
if pl.get(cond.is_empty.key):
ok = False
if not ok:
continue
results.append(type("H", (), {"id": p.id, "score": self.query_score, "payload": pl})())
return type("R", (), {"points": results[:limit]})()
@pytest.fixture
def client(monkeypatch):
"""TestClient con qdrant e embed finti."""
fake = FakeQdrant()
monkeypatch.setattr(gateway.state, "qdrant", fake)
monkeypatch.setattr(gateway, "qdrant", fake)
monkeypatch.setattr(gateway, "API_KEYS", {"test-key"}, raising=False)
monkeypatch.setattr(gateway.state, "ratelimit", {}) # rate limit pulito per test
async def fake_embed(text):
return [0.0] * 1024
monkeypatch.setattr(gateway.state, "embed", fake_embed)
monkeypatch.setattr(gateway, "embed", fake_embed)
from fastapi.testclient import TestClient
with TestClient(gateway.app) as c:
c.fake_qdrant = fake
yield c
def auth_headers():
return {"X-API-Key": "test-key"}
def make_record(**overrides):
base = {
"text": "record di test",
"kind": "fact",
"project_id": "test-proj",
"scope": "agent",
}
base.update(overrides)
return base