groupe-meditation/backend/tests/test_validation_contracts.py

109 lines
4.1 KiB
Python
Raw Normal View History

import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
def read(rel: str) -> str:
return (ROOT / rel).read_text(encoding="utf-8")
class ValidationPlanTests(unittest.TestCase):
def test_validation_plan_is_indexed_and_structured(self):
index = read("docs/00_INDEX.md")
plan = read("docs/96_PLAN_VALIDATION.md")
self.assertIn("96_PLAN_VALIDATION.md", index)
self.assertGreaterEqual(plan.count("Scénario:"), 40)
self.assertEqual(plan.count("Scénario:"), plan.count("Résultats attendus:"))
def test_validation_plan_covers_all_critical_domains(self):
plan = read("docs/96_PLAN_VALIDATION.md")
for section in [
"Accès, groupes et identité",
"Navigation et ergonomie",
"Membres",
"Postes, mandats et permissions",
"Rencontres",
"Assemblées",
"Gouvernance",
"Contributions",
"Dépenses",
"Trésorerie",
"Inventaires, littérature et jetons",
"Événements",
"Rapports PDF et mode papier",
"Journal, historique et notifications",
"Sauvegarde, restauration et exploitation",
"Concurrence et changements simultanés",
]:
with self.subTest(section=section):
self.assertIn(f"## {section}", plan)
class PublicLandingContractTests(unittest.TestCase):
def test_public_landing_contains_only_public_links_and_sysadmin_access(self):
login = read("frontend/src/pages/Login.jsx")
self.assertIn("Choisir un groupe", login)
self.assertIn("Sysadmin", login)
self.assertIn("Charte", login)
self.assertIn("Confidentialité", login)
self.assertIn("Découvrir", login)
self.assertIn("setPage('G14')", login)
self.assertIn("localStorage.removeItem('gm_groupe_id')", login)
self.assertIn('type="password"', login)
def test_group_home_does_not_expose_instance_admin_or_public_policy_links(self):
accueil = read("frontend/src/pages/Accueil.jsx")
for forbidden in ["setPage('G14')", "Charte", "Confidentialité", "Découvrir"]:
with self.subTest(forbidden=forbidden):
self.assertNotIn(forbidden, accueil)
class SafetyContractTests(unittest.TestCase):
def test_api_client_sends_resource_version_for_concurrency_protection(self):
api = read("frontend/src/stores/api.js")
self.assertIn("options.version", api)
self.assertIn("X-Resource-Version", api)
def test_crystallized_reports_guard_meeting_inputs(self):
guarded_files = [
"backend/app/routers/reunions.py",
"backend/app/routers/presences.py",
"backend/app/routers/pv.py",
"backend/app/routers/ventes_litterature.py",
"backend/app/routers/jetons.py",
]
for rel in guarded_files:
source = read(rel)
with self.subTest(router=rel):
self.assertIn("verifier_reunion_non_cristallisee", source)
def test_group_backup_and_restore_are_group_scoped_operations(self):
admin_router = read("backend/app/routers/admin.py")
gestion_groupes = read("frontend/src/pages/GestionGroupes.jsx")
self.assertIn("/groupes/{groupe_id}/backup", admin_router)
self.assertIn("/groupes/{groupe_id}/restore", admin_router)
self.assertIn("creer_sauvegarde_groupe", admin_router)
self.assertIn("restaurer_sauvegarde_groupe", admin_router)
self.assertIn("Réinit", gestion_groupes)
self.assertIn("Restaurer", gestion_groupes)
def test_instance_admin_token_is_kept_out_of_group_context(self):
admin_router = read("backend/app/routers/admin.py")
security = read("backend/app/core/security.py")
self.assertIn("create_token(str(admin.id), None", admin_router)
self.assertIn('subject_type="instance_admin"', admin_router)
self.assertIn('payload.get("typ") == "instance_admin"', security)
self.assertIn("return admin", security)
if __name__ == "__main__":
unittest.main()