"""Юнит-тесты QdrantManager против встроенного Qdrant (:memory:) — реальный бэкенд. Не моки: qdrant-client в local-режиме поднимает in-process Qdrant, поэтому проверяется настоящее поведение коллекции (косинус, upsert, count). """ import numpy as np import pytest pytest.importorskip("qdrant_client") from app.config import settings # noqa: E402 from app.qdrant_manager import QdrantManager # noqa: E402 from qdrant_client import QdrantClient # noqa: E402 DIM = settings.EMBED_DIM def _unit(vecs: np.ndarray) -> np.ndarray: vecs = np.asarray(vecs, dtype=np.float32) norms = np.linalg.norm(vecs, axis=1, keepdims=True) norms[norms == 0] = 1.0 return vecs / norms @pytest.fixture(autouse=True) def in_memory_backend(): """Свежий встроенный Qdrant на каждый тест.""" QdrantManager._reset() QdrantManager._client = QdrantClient(location=":memory:") yield QdrantManager._reset() def test_empty_search_returns_nothing(): assert QdrantManager.search(np.zeros(DIM, dtype=np.float32)) == [] assert QdrantManager.total_vectors() == 0 def test_add_empty_is_noop(): QdrantManager.add_vectors(np.zeros((0, DIM), dtype=np.float32), []) assert QdrantManager.total_vectors() == 0 def test_add_and_self_search_returns_postgres_doc_id(): rng = np.random.default_rng(42) vecs = _unit(rng.standard_normal((3, DIM))) QdrantManager.add_vectors(vecs, [101, 202, 303]) assert QdrantManager.total_vectors() == 3 res = QdrantManager.search(vecs[1], k=1) assert res, "поиск ничего не вернул" top_id, score = res[0] assert top_id == 202 # id точки == doc_id из PostgreSQL assert score == pytest.approx(1.0, abs=1e-3) # self-match: косинус ≈ 1 def test_upsert_is_idempotent_by_doc_id(): rng = np.random.default_rng(0) QdrantManager.add_vectors(_unit(rng.standard_normal((2, DIM))), [1, 2]) updated = _unit(rng.standard_normal((2, DIM))) QdrantManager.add_vectors(updated, [1, 2]) # те же id → перезапись, не дубли assert QdrantManager.total_vectors() == 2 top_id, score = QdrantManager.search(updated[0], k=1)[0] assert top_id == 1 assert score == pytest.approx(1.0, abs=1e-3) def test_search_ranks_nearest_first(): rng = np.random.default_rng(7) v = _unit(rng.standard_normal((1, DIM)))[0] near = _unit((v + 0.01 * rng.standard_normal(DIM))[None, :])[0] far = _unit(rng.standard_normal((1, DIM)))[0] QdrantManager.add_vectors(np.stack([near, far]), [10, 20]) res = QdrantManager.search(v, k=2) assert res[0][0] == 10 # ближайший — near def test_save_is_noop(): QdrantManager.save() # Qdrant персистит сам — вызов не должен падать