from pathlib import Path import pytest from gewu.adapters.sqlite_vectors import SQLiteVectorIndex, VectorIndexError def _vectors() -> tuple[tuple[str, str, tuple[float, ...]], ...]: return ( ("dome-performance", "dome:closest", (1.0, 0.0, 0.0)), ("interactive-video", "video:closest", (0.0, 1.0, 0.0)), ("shared", "shared:closest", (0.9701425001, 0.242535625, 0.0)), ) def _index(path: Path) -> SQLiteVectorIndex: pytest.importorskip("sqlite_vec") return SQLiteVectorIndex(path) def _build(index: SQLiteVectorIndex, generation_id: str, vectors=None) -> None: index.build_generation( generation_id=generation_id, revision="a" * 40, model_id="test-model@revision", artifact_sha256="b" * 64, dimensions=3, vectors=_vectors() if vectors is None else vectors, ) def test_vector_generation_is_scoped_and_switches_atomically(tmp_path: Path) -> None: index = _index(tmp_path / "vectors.sqlite3") _build(index, "generation-1") assert index.activate("generation-1", expected_active=None) is None active = index.active_generation() assert active is not None assert active.generation_id == "generation-1" assert active.revision == "a" * 40 assert active.model_id == "test-model@revision" assert active.artifact_sha256 == "b" * 64 assert active.dimensions == 3 assert active.node_count == 3 scoped = index.search( query=(1.0, 0.0, 0.0), project_ids=("dome-performance",), include_shared=False, limit=10, ) assert [hit.node_id for hit in scoped] == ["dome:closest"] with_shared = index.search( query=(1.0, 0.0, 0.0), project_ids=("dome-performance",), include_shared=True, limit=10, ) assert {hit.node_id for hit in with_shared} == {"dome:closest", "shared:closest"} _build( index, "generation-2", vectors=(("dome-performance", "dome:replacement", (1.0, 0.0, 0.0)),) ) with pytest.raises(VectorIndexError, match="changed"): index.activate("generation-2", expected_active=None) active = index.active_generation() assert active is not None and active.generation_id == "generation-1" assert index.activate("generation-2", expected_active="generation-1") == "generation-1" assert [ hit.node_id for hit in index.search( query=(1.0, 0.0, 0.0), project_ids=("dome-performance",), include_shared=False, limit=10, ) ] == ["dome:replacement"] assert index.activate("generation-1", expected_active="generation-2") == "generation-2" assert [ hit.node_id for hit in index.search( query=(1.0, 0.0, 0.0), project_ids=("dome-performance",), include_shared=False, limit=10, ) ] == ["dome:closest"] with pytest.raises(VectorIndexError, match="Active vector generation"): index.delete_generation("generation-1") index.delete_generation("generation-2") _build(index, "generation-2") def test_vector_generation_rejects_invalid_vectors_before_publish(tmp_path: Path) -> None: index = _index(tmp_path / "vectors.sqlite3") with pytest.raises(VectorIndexError, match="normalized"): _build(index, "bad", vectors=(("dome-performance", "dome:bad", (2.0, 0.0, 0.0)),)) assert index.active_generation() is None assert ( index.search( query=(1.0, 0.0, 0.0), project_ids=("dome-performance",), include_shared=False, limit=10, ) == () )