"""Guide-board pilot ingestion tests (ARTIFACT-STORE-WP-0005).""" from __future__ import annotations import json from collections.abc import AsyncIterator from pathlib import Path from uuid import UUID import pytest import pytest_asyncio from sqlalchemy import create_engine, insert from sqlalchemy.ext.asyncio import create_async_engine from typer.testing import CliRunner from artifactstore.cli import app as cli_app from artifactstore.dataplane import InProcessDataPlane from artifactstore.db.schema import metadata, retention_classes from artifactstore.db.seed import RETENTION_CLASS_SEEDS from artifactstore.events import RegistryViewWriter from artifactstore.manifest import decode as manifest_decode from artifactstore.pilots.guide_board import GUIDE_BOARD_SCHEMA_SLUG, ingest_run from artifactstore.registry import Registry from artifactstore.storage import LocalBackend REPO_ROOT = Path(__file__).resolve().parents[2] FIXTURE = REPO_ROOT / "tests" / "fixtures" / "guide-board" SCHEMA = REPO_ROOT / "schemas" / "guide-board.run.v1.json" @pytest_asyncio.fixture async def registry(tmp_path: Path) -> AsyncIterator[Registry]: db_path = tmp_path / "guide-board.db" engine = create_async_engine(f"sqlite+aiosqlite:///{db_path}") async with engine.begin() as conn: await conn.run_sync(metadata.create_all) for seed in RETENTION_CLASS_SEEDS: await conn.execute(insert(retention_classes).values(**seed)) backend = LocalBackend(tmp_path / "storage", backend_id="local") reg = Registry(engine, InProcessDataPlane(backend), RegistryViewWriter()) try: yield reg finally: await reg.dispose() async def _consume(stream: AsyncIterator[bytes]) -> bytes: out = bytearray() async for chunk in stream: out.extend(chunk) return bytes(out) async def test_guide_board_library_ingest_is_idempotent_and_downloadable( registry: Registry, ) -> None: schema = json.loads(SCHEMA.read_text(encoding="utf-8")) await registry.register_metadata_schema(slug=GUIDE_BOARD_SCHEMA_SLUG, json_schema=schema) first = await ingest_run(FIXTURE, registry=registry) second = await ingest_run(FIXTURE, registry=registry) assert first.package_id assert first.manifest_digest.startswith("blake3:") assert first.manifest_digest == second.manifest_digest assert second.reused_existing is True manifest = manifest_decode( await registry.get_manifest_bytes(UUID(first.package_id), format="cbor") ) assert manifest.package.producer == "guide-board" assert manifest.package.metadata_schema_id is not None assert manifest.retention_summary.retention_class == "release-evidence" assert len(manifest.files) == 8 for file_entry in manifest.files: stream = await registry.get_file(UUID(file_entry.id)) assert await _consume(stream) == (FIXTURE / file_entry.relative_path).read_bytes() state = await registry.get_retention_state(UUID(first.package_id)) assert state.effective_class == "release-evidence" def test_guide_board_cli_ingest_outputs_package_and_digest( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: db_path = tmp_path / "guide-board-cli.db" storage_root = tmp_path / "storage" storage_root.mkdir() sync_engine = create_engine(f"sqlite:///{db_path}", future=True) metadata.create_all(sync_engine) with sync_engine.begin() as conn: conn.execute(insert(retention_classes), [dict(s) for s in RETENTION_CLASS_SEEDS]) sync_engine.dispose() monkeypatch.setenv("ARTIFACTSTORE_DATABASE_URL", f"sqlite+aiosqlite:///{db_path}") monkeypatch.setenv("ARTIFACTSTORE_STORAGE_LOCAL_ROOT", str(storage_root)) result = CliRunner().invoke( cli_app, ["guide-board", "ingest", str(FIXTURE), "--schema", str(SCHEMA)], ) assert result.exit_code == 0, result.output payload = json.loads(result.output) assert payload["package_id"] assert payload["manifest_digest"].startswith("blake3:") assert payload["file_count"] == 8 assert payload["reused_existing"] is False