"""Tests for public API: basic schema validation and serialization. Uses mock database sessions to test the public router logic without requiring a real PostgreSQL connection. """ from __future__ import annotations import uuid from datetime import datetime, timezone from unittest.mock import MagicMock import pytest sqlalchemy = pytest.importorskip("sqlalchemy", reason="sqlalchemy required for public API tests") from app.schemas.document import DocumentFullOut, DocumentOut # noqa: E402 from app.schemas.sanctuary import SanctuaryEntryOut # noqa: E402 # --------------------------------------------------------------------------- # Helpers: mock objects # --------------------------------------------------------------------------- def _make_document_mock( doc_id: uuid.UUID | None = None, slug: str = "licence-g1", title: str = "Licence G1", doc_type: str = "licence", version: str = "2.0.0", doc_status: str = "active", description: str | None = "La licence monetaire", ) -> MagicMock: """Create a mock Document for schema validation.""" doc = MagicMock() doc.id = doc_id or uuid.uuid4() doc.slug = slug doc.title = title doc.doc_type = doc_type doc.version = version doc.status = doc_status doc.description = description doc.ipfs_cid = None doc.chain_anchor = None doc.genesis_json = None doc.created_at = datetime.now(timezone.utc) doc.updated_at = datetime.now(timezone.utc) doc.items = [] return doc def _make_item_mock( item_id: uuid.UUID | None = None, document_id: uuid.UUID | None = None, position: str = "1", item_type: str = "clause", title: str | None = "Article 1", current_text: str = "Texte de l'article", sort_order: int = 0, ) -> MagicMock: """Create a mock DocumentItem for schema validation.""" item = MagicMock() item.id = item_id or uuid.uuid4() item.document_id = document_id or uuid.uuid4() item.position = position item.item_type = item_type item.title = title item.current_text = current_text item.voting_protocol_id = None item.sort_order = sort_order item.section_tag = None item.inertia_preset = "standard" item.is_permanent_vote = True item.created_at = datetime.now(timezone.utc) item.updated_at = datetime.now(timezone.utc) return item def _make_sanctuary_entry_mock( entry_id: uuid.UUID | None = None, entry_type: str = "document", reference_id: uuid.UUID | None = None, title: str | None = "Licence G1 v2.0.0", content_hash: str = "abc123def456", ipfs_cid: str | None = "QmTestCid123", chain_tx_hash: str | None = "0xdeadbeef", ) -> MagicMock: """Create a mock SanctuaryEntry for schema validation.""" entry = MagicMock() entry.id = entry_id or uuid.uuid4() entry.entry_type = entry_type entry.reference_id = reference_id or uuid.uuid4() entry.title = title entry.content_hash = content_hash entry.ipfs_cid = ipfs_cid entry.chain_tx_hash = chain_tx_hash entry.chain_block = 12345 if chain_tx_hash else None entry.metadata_json = None entry.created_at = datetime.now(timezone.utc) return entry # --------------------------------------------------------------------------- # Tests: DocumentOut schema serialization # --------------------------------------------------------------------------- class TestDocumentOutSchema: """Test DocumentOut schema validation from mock objects.""" def test_document_out_basic(self): """DocumentOut validates from a mock document object.""" doc = _make_document_mock() out = DocumentOut.model_validate(doc) assert out.slug == "licence-g1" assert out.title == "Licence G1" assert out.doc_type == "licence" assert out.version == "2.0.0" assert out.status == "active" # items_count is set explicitly after validation (not from model) out.items_count = 0 assert out.items_count == 0 def test_document_out_with_items_count(self): """DocumentOut can have items_count set after validation.""" doc = _make_document_mock() out = DocumentOut.model_validate(doc) out.items_count = 42 assert out.items_count == 42 def test_document_out_all_fields_present(self): """All expected fields are present in the DocumentOut serialization.""" doc = _make_document_mock() out = DocumentOut.model_validate(doc) data = out.model_dump() expected_fields = { "id", "slug", "title", "doc_type", "version", "status", "description", "ipfs_cid", "chain_anchor", "genesis_json", "created_at", "updated_at", "items_count", } assert expected_fields.issubset(set(data.keys())) # --------------------------------------------------------------------------- # Tests: DocumentFullOut schema serialization # --------------------------------------------------------------------------- class TestDocumentFullOutSchema: """Test DocumentFullOut schema validation (document with items).""" def test_document_full_out_empty_items(self): """DocumentFullOut works with an empty items list.""" doc = _make_document_mock() doc.items = [] out = DocumentFullOut.model_validate(doc) assert out.slug == "licence-g1" assert out.items == [] def test_document_full_out_with_items(self): """DocumentFullOut includes items when present.""" doc_id = uuid.uuid4() doc = _make_document_mock(doc_id=doc_id) item1 = _make_item_mock(document_id=doc_id, position="1", sort_order=0) item2 = _make_item_mock(document_id=doc_id, position="2", sort_order=1) doc.items = [item1, item2] out = DocumentFullOut.model_validate(doc) assert len(out.items) == 2 assert out.items[0].position == "1" assert out.items[1].position == "2" # --------------------------------------------------------------------------- # Tests: SanctuaryEntryOut schema serialization # --------------------------------------------------------------------------- class TestSanctuaryEntryOutSchema: """Test SanctuaryEntryOut schema validation.""" def test_sanctuary_entry_out_basic(self): """SanctuaryEntryOut validates from a mock entry.""" entry = _make_sanctuary_entry_mock() out = SanctuaryEntryOut.model_validate(entry) assert out.entry_type == "document" assert out.content_hash == "abc123def456" assert out.ipfs_cid == "QmTestCid123" assert out.chain_tx_hash == "0xdeadbeef" assert out.chain_block == 12345 def test_sanctuary_entry_out_without_ipfs(self): """SanctuaryEntryOut works when IPFS CID is None.""" entry = _make_sanctuary_entry_mock(ipfs_cid=None, chain_tx_hash=None) out = SanctuaryEntryOut.model_validate(entry) assert out.ipfs_cid is None assert out.chain_tx_hash is None assert out.chain_block is None def test_sanctuary_entry_out_all_fields(self): """All expected fields are present in SanctuaryEntryOut.""" entry = _make_sanctuary_entry_mock() out = SanctuaryEntryOut.model_validate(entry) data = out.model_dump() expected_fields = { "id", "entry_type", "reference_id", "title", "content_hash", "ipfs_cid", "chain_tx_hash", "chain_block", "metadata_json", "created_at", } assert expected_fields.issubset(set(data.keys())) def test_sanctuary_entry_types(self): """Different entry_type values are accepted.""" for entry_type in ("document", "decision", "vote_result"): entry = _make_sanctuary_entry_mock(entry_type=entry_type) out = SanctuaryEntryOut.model_validate(entry) assert out.entry_type == entry_type