Compare commits

...
2 changed files with 126 additions and 7 deletions
+99
View File
@@ -0,0 +1,99 @@
name: Integration Tests
# Separate from ci.yml, which is a required check: a slow image pull or a
# container flake must not block unrelated merges.
permissions:
contents: read
on:
pull_request:
paths-ignore:
- 'docs/**'
- 'docs_check.py'
- '**/*.md'
schedule:
- cron: '0 5 * * 1'
workflow_dispatch:
jobs:
pgvector:
name: pgvector (live PostgreSQL)
runs-on: ubuntu-latest
timeout-minutes: 20
services:
postgres:
# pgvector/pgvector:pg16 as published 2026-08-13. Pinned by digest like
# the action pins, though verify-action-pins.sh does not check images.
image: pgvector/pgvector@sha256:ccc6e83d6e35e931dc7c5def2022729d5a6c370318d099181995567ff1fb4d6b
env:
POSTGRES_USER: postgres
POSTGRES_DB: test
# Throwaway container reachable only from this job, so trust auth
# avoids putting a credential in the workflow at all.
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d test"
--health-interval 10s
--health-timeout 5s
--health-retries 10
env:
TEST_PGVECTOR_URL: postgresql://postgres@localhost:5432/test
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: '3.11'
cache: 'pip'
- name: Install semantica with the pgvector extra
run: |
python -m pip install --upgrade pip
pip install -e ".[vectorstore-pgvector]" pytest==9.1.1
- name: Create the vector extension
# PgVectorStore._verify_pgvector_extension() requires it and refuses to
# create it. Doubles as the connectivity gate.
run: |
python - <<'PY'
import os
import psycopg
with psycopg.connect(os.environ["TEST_PGVECTOR_URL"]) as conn:
conn.execute("CREATE EXTENSION IF NOT EXISTS vector")
conn.commit()
print("vector extension ready")
PY
- name: Run the live pgvector suite
run: |
pytest tests/vector_store/test_pgvector_store.py -v -rs \
--junit-xml=junit.xml
- name: Fail if the suite skipped instead of running
# The suite skips itself when it cannot reach Postgres, so pytest would
# exit 0 having run nothing. Without this the job is green either way.
if: always()
run: |
python - <<'PY'
import sys
import xml.etree.ElementTree as ET
root = ET.parse("junit.xml").getroot()
suites = root.findall("testsuite") or [root]
total = sum(int(s.get("tests", 0)) for s in suites)
skipped = sum(int(s.get("skipped", 0)) for s in suites)
if total == 0:
sys.exit("no tests were collected")
if skipped:
sys.exit(f"{skipped} of {total} tests skipped; the live suite did not run")
print(f"{total} tests ran, none skipped")
PY
+27 -7
View File
@@ -10,7 +10,7 @@ To run these tests locally with Docker:
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=test \
-p 5432:5432 \
ankane/pgvector:latest
pgvector/pgvector:pg16
pytest tests/vector_store/test_pgvector_store.py -v
@@ -191,7 +191,9 @@ class TestPgVectorStoreAdd:
ids = store.add(vectors, metadata)
assert len(ids) == 5
assert all(id.startswith("vec_") for id in ids)
assert len(set(ids)) == 5
# add() assigns uuid4 identifiers, not a "vec_" prefix
assert all(uuid.UUID(vector_id) for vector_id in ids)
def test_add_auto_generate_ids(self, store):
"""Test that IDs are auto-generated if not provided."""
@@ -290,19 +292,37 @@ class TestPgVectorStoreSearch:
if not pg_available:
pytest.skip("PostgreSQL not available")
from semantica.vector_store.pgvector_store import PgVectorStore
from semantica.vector_store.pgvector_store import PgVectorStore, psycopg_sql
# setup_vectors is autouse and seeds unique_table_name, and fixtures are
# cached per test, so this needs a table of its own to be empty at all.
empty_table = f"{unique_table_name}_empty"
empty_store = PgVectorStore(
connection_string=TEST_CONNECTION_STRING,
table_name=unique_table_name,
table_name=empty_table,
dimension=128,
distance_metric="cosine",
)
query = np.random.rand(128).astype(np.float32)
results = empty_store.search(query, top_k=5)
try:
query = np.random.rand(128).astype(np.float32)
results = empty_store.search(query, top_k=5)
assert len(results) == 0
assert len(results) == 0
finally:
try:
with empty_store._get_connection() as conn:
cur = conn.cursor()
cur.execute(
psycopg_sql.SQL("DROP TABLE IF EXISTS {}").format(
psycopg_sql.Identifier(empty_table)
)
)
conn.commit()
cur.close()
empty_store.close()
except Exception:
pass
# Cleanup: Drop test table after test completes
# Uses best-effort cleanup - failures are silently ignored since