haiku.rag/tests/store/test_document_meta_split.py
2026-06-12 10:17:17 +03:00

108 lines
4.6 KiB
Python

import lancedb
import pytest
from haiku.rag.store.engine import Store
from haiku.rag.store.exceptions import MigrationRequiredError
from haiku.rag.store.models.document import Document
from haiku.rag.store.repositories.document import DocumentRepository
from tests.store.legacy_documents import (
LegacyDocumentRecord,
seed_legacy_documents,
)
@pytest.mark.asyncio
async def test_create_rolls_back_meta_when_documents_write_fails(temp_db_path):
"""A failed documents write must not leave an orphan document_meta row that
list_all/count would surface (they read document_meta). The rollback must be
targeted — it deletes only the failed row, leaving other documents intact."""
async with Store(temp_db_path, create=True, skip_migration_check=True) as store:
repo = DocumentRepository(store)
# A pre-existing document that must survive the failed create's rollback.
good = await repo.create(Document(content="keep", uri="mem://keep"))
assert good.id is not None
original_add = store.documents_table.add
async def boom(*_args, **_kwargs):
raise RuntimeError("documents write failed")
store.documents_table.add = boom
with pytest.raises(RuntimeError, match="documents write failed"):
await repo.create(Document(content="x", uri="mem://ghost"))
store.documents_table.add = original_add
# The ghost's meta row was deleted; the good document is untouched.
assert await repo.count() == 1
assert [d.id for d in await repo.list_all()] == [good.id]
assert await store.document_meta_table.count_rows() == 1
assert await repo.get_by_uri("mem://ghost") is None
fetched = await repo.get_by_id(good.id)
assert fetched is not None and fetched.uri == "mem://keep"
@pytest.mark.asyncio
async def test_update_missing_id_does_not_create_ghost(temp_db_path):
"""update()/update_meta() for an id with no documents row must not insert a
document_meta row — otherwise it would show up in list_all/count while
get_by_id returns None."""
async with Store(temp_db_path, create=True, skip_migration_check=True) as store:
repo = DocumentRepository(store)
await repo.update(Document(id="missing", content="x", uri="u"))
assert await repo.count() == 0
assert await repo.list_all() == []
assert await store.documents_table.count_rows() == 0
assert await store.document_meta_table.count_rows() == 0
assert await repo.get_by_id("missing") is None
@pytest.mark.asyncio
async def test_opening_legacy_db_raises_migration_without_mutating(temp_db_path):
"""Opening a pre-0.57 DB (no document_meta) must raise MigrationRequiredError
up front — in both writable and read-only mode — and must not mutate the DB
by creating the new table on open."""
async with Store(temp_db_path, create=True, skip_migration_check=True) as store:
await seed_legacy_documents(
store,
[LegacyDocumentRecord(id="d", content="x", uri="u", metadata="{}")],
)
# A real pre-0.57 DB has no document_meta table.
await store.db.drop_table("document_meta")
await store.set_haiku_version("0.56.0")
# Writable open: pending migration surfaces before any table creation.
with pytest.raises(MigrationRequiredError):
async with Store(temp_db_path):
pass
# Read-only open: must also be MigrationRequiredError (not ReadOnlyError).
with pytest.raises(MigrationRequiredError):
async with Store(temp_db_path, read_only=True):
pass
# The failed opens did not create document_meta.
raw = await lancedb.connect_async(str(temp_db_path))
assert "document_meta" not in (await raw.list_tables()).tables
@pytest.mark.asyncio
async def test_migrate_creates_and_populates_document_meta(temp_db_path):
"""The migrate path (skip_migration_check) still creates and fills
document_meta for a legacy DB."""
async with Store(temp_db_path, create=True, skip_migration_check=True) as store:
await seed_legacy_documents(
store,
[LegacyDocumentRecord(id="d", content="x", uri="u", metadata="{}")],
)
await store.db.drop_table("document_meta")
await store.set_haiku_version("0.56.0")
async with Store(temp_db_path, skip_migration_check=True) as store:
await store.migrate()
assert "document_meta" in (await store.db.list_tables()).tables
repo = DocumentRepository(store)
doc = await repo.get_by_id("d")
assert doc is not None and doc.uri == "u"