"""Tests unitaires pour l'équivalence des UIDs entre les sources iCal et pronotepy. Ce module vérifie que les fonctions de normalisation et de génération d'UIDs produisent les mêmes résultats pour les mêmes données, garantissant qu'aucun doublon ou suppression artificielle ne se produit lors du changement de source. """ from __future__ import annotations from datetime import datetime from pathlib import Path from icalendar import Calendar from pronote_sync.utils.uid import generate_deterministic_uid, normalize_pronote_uid # --- Test 1: normalize_pronote_uid strips temporal suffix --- def test_normalize_pronote_uid_strips_temporal_suffix() -> None: """Vérifie que normalize_pronote_uid produit un UID canonique stable. Un UID brut Pronote avec suffixe temporel comme ``L-12345-20260905T120000Z-Index-Education`` doit être normalisé en ``L-12345``. :return: None """ raw_uid = "L-12345-20260905T120000Z-Index-Education" normalized = normalize_pronote_uid(raw_uid) assert normalized == "L-12345" def test_normalize_pronote_uid_strips_education_suffix_only() -> None: """Vérifie que normalize_pronote_uid retire aussi le suffixe -Index-Education seul. :return: None """ raw_uid = "L-12345-Index-Education" normalized = normalize_pronote_uid(raw_uid) assert normalized == "L-12345" def test_normalize_pronote_uid_idempotent() -> None: """Vérifie que normalize_pronote_uid est idempotente. :return: None """ raw_uid = "L-12345" normalized = normalize_pronote_uid(raw_uid) assert normalized == "L-12345" # --- Test 2: generate_deterministic_uid is stable --- def test_generate_deterministic_uid_is_stable() -> None: """Vérifie que generate_deterministic_uid produit le même UID pour les mêmes entrées. :return: None """ start = datetime(2026, 9, 5, 8, 0, 0) end = datetime(2026, 9, 5, 9, 0, 0) subject = "Mathématiques" teachers = ["M. Dupont"] rooms = ["204"] group = "Classe entière" uid1 = generate_deterministic_uid( start=start, end=end, subject=subject, teachers=teachers, rooms=rooms, group=group ) uid2 = generate_deterministic_uid( start=start, end=end, subject=subject, teachers=teachers, rooms=rooms, group=group ) assert uid1 == uid2 assert len(uid1) == 12 assert all(c in "0123456789abcdef" for c in uid1) def test_generate_deterministic_uid_ignores_order() -> None: """Vérifie que l'ordre des enseignants/salles n'affecte pas le résultat. :return: None """ start = datetime(2026, 9, 5, 8, 0, 0) end = datetime(2026, 9, 5, 9, 0, 0) subject = "Mathématiques" uid1 = generate_deterministic_uid( start=start, end=end, subject=subject, teachers=["M. Dupont", "Mme Martin"], rooms=["204", "205"], group=None, ) uid2 = generate_deterministic_uid( start=start, end=end, subject=subject, teachers=["Mme Martin", "M. Dupont"], rooms=["205", "204"], group=None, ) assert uid1 == uid2 # --- Test 3: iCal and pronotepy produce same UID with raw UID --- def test_ical_and_pronotepy_produce_same_uid_with_raw_uid() -> None: """Vérifie que iCal et pronotepy produisent le même UID avec un UID brut. Comme les deux sources utilisent normalize_pronote_uid, le résultat doit être identique. :return: None """ raw_uid = "Edt_12345@index-education.net-20260905T120000Z-Index-Education" # Simulation du chemin iCal ical_normalized = normalize_pronote_uid(raw_uid) # Simulation du chemin pronotepy pronotepy_normalized = normalize_pronote_uid(raw_uid) assert ical_normalized == pronotepy_normalized assert ical_normalized == "Edt_12345@index-education.net" # --- Test 4: iCal and pronotepy produce same UID with fallback --- def test_ical_and_pronotepy_produce_same_uid_with_fallback() -> None: """Vérifie que iCal et pronotepy produisent le même UID déterministe en l'absence d'UID brut. :return: None """ start = datetime(2026, 9, 5, 8, 0, 0) end = datetime(2026, 9, 5, 9, 0, 0) subject = "Mathématiques" teachers = ["M. Dupont"] rooms = ["204"] group = "Classe entière" # Simulation du chemin iCal (parse_ical utilise generate_deterministic_uid) ical_uid = generate_deterministic_uid( start=start, end=end, subject=subject, teachers=teachers, rooms=rooms, group=group ) # Simulation du chemin pronotepy (get_lessons utilise generate_deterministic_uid) pronotepy_uid = generate_deterministic_uid( start=start, end=end, subject=subject, teachers=teachers, rooms=rooms, group=group ) assert ical_uid == pronotepy_uid # --- Test 5: UID from iCal fixture is normalized --- def test_uid_from_ical_fixture_is_normalized() -> None: """Vérifie que les UIDs extraits du fixture pronote-4e.ics sont normalisés correctement. :return: None """ fixture_path = Path(__file__).parent.parent / "fixtures" / "pronote-4e.ics" with open(fixture_path, "rb") as f: cal = Calendar.from_ical(f.read()) normalized_uids = [] for component in cal.walk("VEVENT"): raw_uid = str(component.get("UID")) normalized = normalize_pronote_uid(raw_uid) normalized_uids.append(normalized) # Vérifie que tous les UIDs sont normalisés (plus courts que les UIDs bruts) assert len(normalized_uids) >= 2 # Vérifie que les UIDs normalisés ne contiennent pas de suffixes temporels for uid in normalized_uids: assert "-Index-Education" not in uid assert "-2026" not in uid # Pas de suffixe temporel # Vérifie que le premier UID est bien normalisé assert "Edt_12345@index-education.net" in normalized_uids