Files
decision/backend/app/tests/test_public_api.py
Yvv c19c1aa55e Restructure Engagement Forgeron + fix GenesisBlock + InertiaSlider
- Seed: restructure Engagement Forgeron (51→59 items) avec 3 nouvelles
  sections: Engagements fondamentaux (EF1-EF3), Engagements techniques
  (ET1-ET3), Qualification (Q0-Q1) liée au protocole Embarquement
- Seed: ajout protocole Embarquement Forgeron (5 jalons: candidature,
  miroir, évaluation, certification Smith, mise en ligne)
- GenesisBlock: fix lisibilité — fond mood-surface teinté accent au lieu
  de mood-text inversé, texte mood-aware au lieu de rgba blanc hardcodé
- InertiaSlider: mini affiche "Inertie" sous le curseur, compact en
  width:fit-content pour s'adapter au label
- Frontend: ajout section qualification dans SECTION_META/SECTION_ORDER
- Pages, composants et tests des sprints précédents

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 03:44:33 +01:00

227 lines
7.8 KiB
Python

"""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