"""PostgreSQL integration tests for atomic document publication.""" from __future__ import annotations from uuid import uuid4 import pytest import pytest_asyncio from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from ragcore.application.documents import ( ArtifactRecord, ChunkRecord, DocumentPublicationService, DocumentRecord, DocumentVersionRecord, DocumentVersionState, ) from ragcore.application.sources import implicit_source_record from ragcore.config import Settings from ragcore.infrastructure.database import ( create_database_engine, create_session_factory, ) from ragcore.infrastructure.database.content import PostgresDocumentRepository from ragcore.infrastructure.database.sources import PostgresSourceRegistry @pytest_asyncio.fixture async def publication_context() -> tuple[ PostgresDocumentRepository, async_sessionmaker[AsyncSession] ]: engine = create_database_engine(Settings(env="test")) factory = create_session_factory(engine) try: yield PostgresDocumentRepository(factory), factory finally: await engine.dispose() async def _register_source(factory, document) -> None: # type: ignore[no-untyped-def] """The content schema requires a registry row for every document's source.""" await PostgresSourceRegistry(factory).ensure_source( implicit_source_record( source_id=document.source_id, workspace_id=document.workspace_id, space_id=document.space_id, ) ) def version(document_id, state: DocumentVersionState, *, fingerprint: str) -> DocumentVersionRecord: return DocumentVersionRecord( id=uuid4(), document_id=document_id, source_fingerprint=fingerprint, content_hash=fingerprint, parser_version="parser-v1", chunk_profile_version="prose-v1", state=state, ) @pytest.mark.integration async def test_publication_supersedes_previous_and_is_idempotent( publication_context: tuple[PostgresDocumentRepository, async_sessionmaker[AsyncSession]], ) -> None: repository, factory = publication_context document = DocumentRecord(uuid4(), uuid4(), uuid4(), "source-1", f"doc-{uuid4()}", "Runbook") await _register_source(factory, document) await repository.create_document(document) first = version(document.id, DocumentVersionState.VALIDATED, fingerprint="1" * 64) second = version(document.id, DocumentVersionState.VALIDATED, fingerprint="2" * 64) await repository.create_version(first) await repository.create_version(second) service = DocumentPublicationService(repository) artifact = ArtifactRecord(uuid4(), second.id, "canonical", "2" * 64, "parsed/key", 10) chunk = ChunkRecord(uuid4(), second.id, "/blocks/0000", "3" * 64, "prose-v1", "safe", 1) await service.publish_validated(first.id, artifacts=(), chunks=()) await service.publish_validated(second.id, artifacts=(artifact,), chunks=(chunk,)) await service.publish_validated(second.id, artifacts=(artifact,), chunks=(chunk,)) assert (await repository.list_searchable_versions(document.id)) == ( await repository.get_version(second.id), ) assert (await repository.get_version(first.id)).state is DocumentVersionState.SUPERSEDED @pytest.mark.integration async def test_failed_version_does_not_replace_previous_active_version( publication_context: tuple[PostgresDocumentRepository, async_sessionmaker[AsyncSession]], ) -> None: repository, factory = publication_context document = DocumentRecord(uuid4(), uuid4(), uuid4(), "source-2", f"doc-{uuid4()}", "Stable") await _register_source(factory, document) await repository.create_document(document) active = version(document.id, DocumentVersionState.VALIDATED, fingerprint="4" * 64) failed = version(document.id, DocumentVersionState.FAILED, fingerprint="5" * 64) await repository.create_version(active) await repository.create_version(failed) service = DocumentPublicationService(repository) await service.publish_validated(active.id, artifacts=(), chunks=()) with pytest.raises(RuntimeError): await service.publish_validated(failed.id, artifacts=(), chunks=()) assert len(await repository.list_searchable_versions(document.id)) == 1 assert (await repository.list_searchable_versions(document.id))[0].id == active.id @pytest.mark.integration async def test_source_deletion_retires_active_version_immediately( publication_context: tuple[PostgresDocumentRepository, async_sessionmaker[AsyncSession]], ) -> None: repository, factory = publication_context document = DocumentRecord(uuid4(), uuid4(), uuid4(), "source-3", f"doc-{uuid4()}", "Deleted") await _register_source(factory, document) await repository.create_document(document) active = version(document.id, DocumentVersionState.VALIDATED, fingerprint="6" * 64) await repository.create_version(active) await DocumentPublicationService(repository).publish_validated( active.id, artifacts=(), chunks=() ) await DocumentPublicationService(repository).mark_source_deleted(document.id) assert await repository.list_searchable_versions(document.id) == () assert (await repository.get_version(active.id)).state is DocumentVersionState.RETIRED