haiku.rag/tests/store/test_time_travel.py
Yiorgis Gozadinos 82fd10e0ee
Migrate LanceDB to native async API
Convert all LanceDB operations from sync calls wrapped in async
functions to the native async API (connect_async, AsyncConnection,
AsyncTable, AsyncQuery). Database I/O no longer blocks the event loop.

- Store and HaikuRAG use async context managers (async with). Store
  initialization is deferred to __aenter__; direct construction
  without async with is no longer supported.
- Index creation uses config objects (FTS, BTree, IvfPq) instead of
  string-based index_type parameter.
- Upgrade callbacks are async.
- HaikuRAG tracks background vacuum tasks and awaits them in __aexit__
  and before destructive rebuild operations to avoid races with
  concurrent table mutations.
- temp_db_path fixture uses pytest's tmp_path for reliable async
  cleanup.
2026-04-24 14:42:52 +03:00

96 lines
3.7 KiB
Python

import asyncio
from datetime import UTC, datetime, timedelta
import pytest
from haiku.rag.store import ReadOnlyError, Store
from haiku.rag.store.models import Document
from haiku.rag.store.repositories.document import DocumentRepository
class TestStoreTimeTravel:
@pytest.mark.asyncio
async def test_store_with_before_is_read_only(self, temp_db_path):
"""Store with before parameter is automatically read-only."""
async with Store(temp_db_path, create=True):
pass
before = datetime.now(UTC) + timedelta(hours=1)
async with Store(temp_db_path, before=before) as store:
assert store.is_read_only is True
@pytest.mark.asyncio
async def test_store_before_raises_on_write(self, temp_db_path):
"""Store with before parameter raises on write operations."""
async with Store(temp_db_path, create=True):
pass
before = datetime.now(UTC) + timedelta(hours=1)
async with Store(temp_db_path, before=before) as store:
with pytest.raises(ReadOnlyError):
store._assert_writable()
@pytest.mark.asyncio
async def test_store_before_checks_out_historical_state(self, temp_db_path):
"""Store with before parameter checks out tables to historical state."""
async with Store(temp_db_path, create=True) as store:
repo = DocumentRepository(store)
await repo.create(Document(content="First document"))
versions_after_first = await store.list_table_versions("documents")
latest_version = max(versions_after_first, key=lambda v: v["version"])
time_after_first = latest_version["timestamp"]
await asyncio.sleep(0.5)
await repo.create(Document(content="Second document"))
versions_after_second = await store.list_table_versions("documents")
assert len(versions_after_second) > len(versions_after_first)
async with Store(temp_db_path, before=time_after_first) as store:
repo = DocumentRepository(store)
docs = await repo.list_all(include_content=True)
assert len(docs) == 1
assert docs[0].content == "First document"
async with Store(temp_db_path) as store:
repo = DocumentRepository(store)
docs = await repo.list_all()
assert len(docs) == 2
@pytest.mark.asyncio
async def test_store_before_no_version_raises(self, temp_db_path):
"""Store with before datetime before any version raises ValueError."""
async with Store(temp_db_path, create=True):
pass
before = datetime(2000, 1, 1, tzinfo=UTC)
with pytest.raises(ValueError) as exc_info:
async with Store(temp_db_path, before=before):
pass
assert "No data exists before" in str(exc_info.value)
@pytest.mark.asyncio
async def test_current_table_versions_returns_versions(self, temp_db_path):
"""current_table_versions returns dict of table versions."""
async with Store(temp_db_path, create=True) as store:
versions = await store.current_table_versions()
assert "documents" in versions
assert "chunks" in versions
assert "settings" in versions
assert all(isinstance(v, int) for v in versions.values())
@pytest.mark.asyncio
async def test_list_table_versions_returns_history(self, temp_db_path):
"""list_table_versions returns version history for a table."""
async with Store(temp_db_path, create=True) as store:
versions = await store.list_table_versions("documents")
assert len(versions) >= 1
for v in versions:
assert "version" in v
assert "timestamp" in v