Files
forge/apps/api/tests/test_pgvector_store.py
nihalashetty ae67bff5a3 feat: deep-agent canvas, live observability, and multi-environment tooling
Self-hosted platform for building, testing, and shipping LangChain/LangGraph agents. Deep-agent sub-agents on the canvas, a live tracing/observability timeline, auto-provisioned built-in tools with import/export, per-environment tool variables, streamed evaluations, and per-user auth token forwarding.
2026-07-28 01:49:19 +05:30

85 lines
3.2 KiB
Python

"""Unit tests for the pgvector backend's selection + Chroma-where translation.
No live Postgres here: these cover the pure, bug-prone logic (Chroma where-dict -> SQL
predicate, vector literal encoding, sync-DSN derivation) and the make_store() backend switch.
The dense/hybrid search SQL shares its fusion + where semantics with the Chroma path that the
other knowledge tests already exercise end to end.
"""
from __future__ import annotations
import pytest
from forge.config import settings
from forge.knowledge import store as store_mod
from forge.knowledge.pgvector_store import (
PgVectorStore,
_sync_dsn,
_to_vector_literal,
_translate_where,
)
from forge.knowledge.store import make_store
def test_translate_where_and_eq():
params: list = []
sql = _translate_where({"$and": [{"tenant_id": {"$eq": "t1"}}, {"project_id": {"$eq": "p1"}}]}, params)
assert "metadata->>%s" in sql and " AND " in sql
# field names AND values are bound positionally, never string-formatted
assert params == ["tenant_id", "t1", "project_id", "p1"]
def test_translate_where_in_and_shorthand():
params: list = []
sql = _translate_where({"kind": {"$in": ["faq", "hours"]}}, params)
assert "= ANY(%s)" in sql
assert params == ["kind", ["faq", "hours"]]
# {field: value} shorthand behaves as $eq
params2: list = []
_translate_where({"source_id": "s1"}, params2)
assert params2 == ["source_id", "s1"]
def test_translate_where_empty_is_true():
assert _translate_where(None, []) == "TRUE"
assert _translate_where({}, []) == "TRUE"
def test_translate_where_rejects_unknown_operator():
# fail closed: an operator we don't model must never silently drop the filter
with pytest.raises(ValueError):
_translate_where({"score": {"$gt": 0.5}}, [])
def test_vector_literal_encoding():
assert _to_vector_literal([0.0, 1.5, -2.0]) == "[0.0,1.5,-2.0]"
def test_sync_dsn_strips_async_driver(monkeypatch):
monkeypatch.setattr(settings, "database_url", "postgresql+asyncpg://u:p@h:5432/db", raising=False)
assert _sync_dsn() == "postgresql://u:p@h:5432/db"
def test_sync_dsn_rejects_sqlite(monkeypatch):
monkeypatch.setattr(settings, "database_url", "sqlite+aiosqlite:///x.db", raising=False)
with pytest.raises(RuntimeError):
_sync_dsn()
def test_make_store_defaults_to_chroma(monkeypatch):
# default backend selects Chroma; stub the ctor so no real on-disk client is built
monkeypatch.setattr(settings, "vector_backend", "chroma", raising=False)
marker = object()
monkeypatch.setattr(store_mod, "ChromaStore", lambda collection="forge_kb": marker)
assert make_store(collection="forge_kb_8") is marker
def test_make_store_selects_pgvector(monkeypatch):
monkeypatch.setattr(settings, "vector_backend", "pgvector", raising=False)
monkeypatch.setattr(settings, "database_url", "postgresql+asyncpg://u:p@h/db", raising=False)
monkeypatch.setattr(PgVectorStore, "_ensure_schema", lambda self: None) # skip the DB bootstrap
st = make_store(collection="forge_kb_16")
assert isinstance(st, PgVectorStore)
assert st._collection == "forge_kb_16"
assert st._dsn == "postgresql://u:p@h/db"