test_retention.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. """Tests unitaires pour les algorithmes de rétention (fonctions pures, sans Flask)."""
  2. from datetime import datetime, timedelta
  3. def days_ago(n):
  4. return datetime.utcnow() - timedelta(days=n)
  5. # ---------------------------------------------------------------------------
  6. # _retention_count
  7. # ---------------------------------------------------------------------------
  8. class TestRetentionCount:
  9. def test_supprime_les_plus_anciens(self):
  10. from retention import _retention_count
  11. archives = [
  12. "a_20260101.tar", "a_20260102.tar", "a_20260103.tar",
  13. "a_20260104.tar", "a_20260105.tar",
  14. ]
  15. assert _retention_count(archives, 3) == ["a_20260101.tar", "a_20260102.tar"]
  16. def test_moins_que_n_garde_tout(self):
  17. from retention import _retention_count
  18. archives = ["a_20260101.tar", "a_20260102.tar"]
  19. assert _retention_count(archives, 5) == []
  20. def test_exactement_n_garde_tout(self):
  21. from retention import _retention_count
  22. archives = ["a_20260101.tar", "a_20260102.tar", "a_20260103.tar"]
  23. assert _retention_count(archives, 3) == []
  24. def test_liste_vide(self):
  25. from retention import _retention_count
  26. assert _retention_count([], 5) == []
  27. def test_keep_1_supprime_tous_sauf_dernier(self):
  28. from retention import _retention_count
  29. archives = ["a_20260101.tar", "a_20260102.tar", "a_20260103.tar"]
  30. assert _retention_count(archives, 1) == ["a_20260101.tar", "a_20260102.tar"]
  31. # ---------------------------------------------------------------------------
  32. # _retention_daily
  33. # ---------------------------------------------------------------------------
  34. class TestRetentionDaily:
  35. def test_supprime_archives_trop_anciennes(self):
  36. from retention import _retention_daily
  37. archives = [
  38. f"a_{days_ago(35).strftime('%Y%m%d')}.tar",
  39. f"a_{days_ago(31).strftime('%Y%m%d')}.tar",
  40. f"a_{days_ago(10).strftime('%Y%m%d')}.tar",
  41. f"a_{days_ago(1).strftime('%Y%m%d')}.tar",
  42. ]
  43. result = _retention_daily(archives, 30)
  44. assert archives[0] in result
  45. assert archives[1] in result
  46. assert archives[2] not in result
  47. assert archives[3] not in result
  48. def test_deduplique_meme_jour(self):
  49. from retention import _retention_daily
  50. today = days_ago(0).strftime("%Y%m%d")
  51. a1 = f"a_{today}.tar"
  52. a2 = f"a_{today}_2.tar"
  53. archives = sorted([a1, a2])
  54. result = _retention_daily(archives, 30)
  55. assert len(result) == 1
  56. def test_garde_une_par_jour_dans_fenetre(self):
  57. from retention import _retention_daily
  58. archives = [f"a_{days_ago(i).strftime('%Y%m%d')}.tar" for i in range(9, -1, -1)]
  59. assert _retention_daily(archives, 30) == []
  60. def test_liste_vide(self):
  61. from retention import _retention_daily
  62. assert _retention_daily([], 30) == []
  63. def test_toutes_hors_fenetre(self):
  64. from retention import _retention_daily
  65. archives = [
  66. f"a_{days_ago(60).strftime('%Y%m%d')}.tar",
  67. f"a_{days_ago(45).strftime('%Y%m%d')}.tar",
  68. ]
  69. result = _retention_daily(archives, 30)
  70. assert set(result) == set(archives)
  71. # ---------------------------------------------------------------------------
  72. # _retention_gfs
  73. # ---------------------------------------------------------------------------
  74. class TestRetentionGFS:
  75. def test_liste_vide(self):
  76. from retention import _retention_gfs
  77. assert _retention_gfs([], {"daily": 7, "weekly": 4, "monthly": 12}) == []
  78. def test_tous_dans_fenetre_daily_gardes(self):
  79. from retention import _retention_gfs
  80. archives = [f"a_2026050{i}.tar" for i in range(1, 8)] # 7 archives
  81. assert _retention_gfs(archives, {"daily": 7, "weekly": 2, "monthly": 2}) == []
  82. def test_daily_supprime_au_dela_de_n(self):
  83. from retention import _retention_gfs
  84. # 15 archives consécutives (mai 01-15), daily=7
  85. # Le mensuel et hebdo tombent dans la fenêtre daily pour cet exemple
  86. archives = [f"a_202605{i:02d}.tar" for i in range(1, 16)]
  87. to_delete = _retention_gfs(archives, {"daily": 7, "weekly": 1, "monthly": 1})
  88. # Les 7 plus récentes (mai 9-15) sont gardées par daily
  89. for i in range(9, 16):
  90. assert f"a_202605{i:02d}.tar" not in to_delete
  91. def test_monthly_sauve_archive_ancienne(self):
  92. from retention import _retention_gfs
  93. # Archives sur 5 mois distincts
  94. archives = [
  95. "a_20260115.tar", # janvier (plus vieux)
  96. "a_20260215.tar", # février
  97. "a_20260315.tar", # mars
  98. "a_20260415.tar", # avril
  99. "a_20260510.tar", # mai
  100. "a_20260511.tar", # mai (plus récent)
  101. ]
  102. # daily=2 garde mai11+mai10, weekly=1 garde semaine de mai11,
  103. # monthly=3 garde mai→mai11, avr→avr15, mars→mars15
  104. # Supprimés : jan15, fév15
  105. to_delete = _retention_gfs(archives, {"daily": 2, "weekly": 1, "monthly": 3})
  106. assert "a_20260115.tar" in to_delete
  107. assert "a_20260215.tar" in to_delete
  108. assert "a_20260315.tar" not in to_delete
  109. assert "a_20260415.tar" not in to_delete
  110. assert "a_20260510.tar" not in to_delete
  111. assert "a_20260511.tar" not in to_delete
  112. assert len(to_delete) == 2
  113. def test_weekly_un_seul_representant_par_semaine(self):
  114. from retention import _retention_gfs
  115. # Semaine 19 (mai 04-10) + semaine 20 (mai 11) — vérifiées ISO
  116. # 2026-05-04 est un lundi (début de semaine 19)
  117. # 2026-05-11 est un lundi (début de semaine 20)
  118. archives = [
  119. "a_20260504.tar", "a_20260505.tar", "a_20260506.tar",
  120. "a_20260507.tar", "a_20260508.tar", "a_20260509.tar",
  121. "a_20260510.tar", "a_20260511.tar",
  122. ]
  123. # daily=3 : mai11, mai10, mai09
  124. # weekly=2 : sem20→mai11, sem19→mai10 (déjà en daily)
  125. # monthly=1 : mai→mai11
  126. # Keepers : {mai09, mai10, mai11}
  127. # Supprimés : {mai04, mai05, mai06, mai07, mai08}
  128. to_delete = _retention_gfs(archives, {"daily": 3, "weekly": 2, "monthly": 1})
  129. assert "a_20260511.tar" not in to_delete
  130. assert "a_20260510.tar" not in to_delete
  131. assert "a_20260509.tar" not in to_delete
  132. assert "a_20260504.tar" in to_delete
  133. assert "a_20260508.tar" in to_delete
  134. assert len(to_delete) == 5
  135. def test_une_archive_peut_couvrir_plusieurs_niveaux(self):
  136. from retention import _retention_gfs
  137. # Une seule archive : elle doit être conservée même avec daily=1, weekly=1, monthly=1
  138. archives = ["a_20260511.tar"]
  139. assert _retention_gfs(archives, {"daily": 1, "weekly": 1, "monthly": 1}) == []
  140. def test_defaults_utilises_si_config_vide(self):
  141. from retention import _retention_gfs
  142. # Config vide → defaults daily=7, weekly=4, monthly=12
  143. # Avec 3 archives, tout doit être gardé
  144. archives = ["a_20260509.tar", "a_20260510.tar", "a_20260511.tar"]
  145. assert _retention_gfs(archives, {}) == []