109 lines
4.1 KiB
Python
109 lines
4.1 KiB
Python
|
|
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()
|