import unittest import sys from pathlib import Path from types import SimpleNamespace sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from app.core.decision_types import type_decision_depuis_proposition from app.core.financial_position import PositionTresorerie from app.core.sanitization import safe_query_params from app.models.poste import Poste class DecisionTypeTests(unittest.TestCase): def test_resolution_prefix(self): prop = SimpleNamespace(numero_resolution="RES-2026-001", candidature_id=None) self.assertEqual(type_decision_depuis_proposition(prop), "resolution") def test_nomination_from_candidature(self): prop = SimpleNamespace(numero_resolution=None, candidature_id="candidate-id") self.assertEqual(type_decision_depuis_proposition(prop), "nomination") def test_contribution_prefix(self): prop = SimpleNamespace(numero_resolution="DON-2026-001", candidature_id=None) self.assertEqual(type_decision_depuis_proposition(prop), "contribution") def test_plain_proposition_without_decision_marker(self): prop = SimpleNamespace(numero_resolution=None, candidature_id=None) self.assertEqual(type_decision_depuis_proposition(prop), "proposition") class PositionTresorerieTests(unittest.TestCase): def test_available_bank_excludes_reserves_and_commitments(self): position = PositionTresorerie( solde_banque=100.0, encaisse=25.0, total_reserves=40.0, engagements_non_debites=15.0, ) self.assertEqual(position.disponible_bancaire, 45.0) self.assertEqual(position.disponible_total, 70.0) class AuditTests(unittest.TestCase): def test_sensitive_query_params_are_masked(self): request = SimpleNamespace(query_params={"pin": "1234", "membre_id": "abc"}) self.assertEqual(safe_query_params(request.query_params), {"pin": "***", "membre_id": "abc"}) class MultiTenantTests(unittest.TestCase): def test_postes_are_group_scoped(self): self.assertIn("groupe_id", Poste.__table__.columns) self.assertFalse(Poste.__table__.columns["groupe_id"].nullable) if __name__ == "__main__": unittest.main()