- 9 nouveaux fichiers de tests (persistance, translations, fiches, indices, IHH) - Enrichissement des tests existants (graph_utils, gitea, widgets, tickets) - 67→448 tests, tous passent Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
796 lines
28 KiB
Python
796 lines
28 KiB
Python
"""Tests unitaires pour le module utils.persistance.
|
|
|
|
Ces tests verifient les fonctions de persistance JSON utilisees pour
|
|
sauvegarder et recuperer l'etat des sessions Streamlit.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from datetime import date
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
# On mock les dependances externes AVANT d'importer le module sous test.
|
|
# persistance.py fait au niveau module :
|
|
# - import streamlit as st
|
|
# - from utils.translations import _
|
|
# - from dotenv import load_dotenv ; load_dotenv(".env")
|
|
# Ces mocks garantissent que les tests fonctionnent meme si ces paquets
|
|
# ne sont pas installes dans l'environnement de test.
|
|
|
|
if "streamlit" not in sys.modules:
|
|
sys.modules["streamlit"] = MagicMock()
|
|
|
|
if "dotenv" not in sys.modules:
|
|
sys.modules["dotenv"] = MagicMock()
|
|
|
|
if "utils.translations" not in sys.modules:
|
|
_mock_translations = MagicMock()
|
|
_mock_translations._ = lambda key: key
|
|
sys.modules["utils.translations"] = _mock_translations
|
|
|
|
# Maintenant on peut importer le module sous test
|
|
from utils.persistance import (
|
|
_get_champ,
|
|
_maj_champ,
|
|
_supprime_champ,
|
|
get_full_structure,
|
|
get_session_id,
|
|
update_session_paths,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _creer_fichier_json(chemin: Path, contenu: dict) -> Path:
|
|
"""Cree un fichier JSON avec le contenu donne."""
|
|
chemin.write_text(json.dumps(contenu, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
return chemin
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests pour get_session_id
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGetSessionId:
|
|
"""Tests pour la fonction get_session_id."""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_retourne_session_id_depuis_headers(self, mock_st):
|
|
"""Test que le session ID est recupere depuis les headers HTTP."""
|
|
mock_st.context.headers.get.return_value = "abc-123-session"
|
|
|
|
resultat = get_session_id()
|
|
|
|
assert resultat == "abc-123-session"
|
|
mock_st.context.headers.get.assert_called_once_with("x-session-id", "anonymous")
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_retourne_anonymous_si_header_absent(self, mock_st):
|
|
"""Test le fallback sur 'anonymous' quand le header est absent."""
|
|
mock_st.context.headers.get.return_value = "anonymous"
|
|
|
|
resultat = get_session_id()
|
|
|
|
assert resultat == "anonymous"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests pour update_session_paths
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestUpdateSessionPaths:
|
|
"""Tests pour la fonction update_session_paths."""
|
|
|
|
@patch("utils.persistance.get_session_id", return_value="test-session-42")
|
|
@patch("utils.persistance.os.getenv", return_value="statut_general.json")
|
|
@patch("utils.persistance.Path.mkdir")
|
|
def test_initialise_chemins_session(self, mock_mkdir, mock_getenv, mock_get_sid):
|
|
"""Test que les variables globales sont correctement initialisees."""
|
|
import utils.persistance as mod
|
|
|
|
update_session_paths()
|
|
|
|
assert mod.SAVE_STATUT == "statut_general.json"
|
|
assert mod.SAVE_SESSIONS_PATH == Path("tmp/sessions/test-session-42")
|
|
assert mod.SAVE_STATUT_PATH == Path("tmp/sessions/test-session-42/statut_general.json")
|
|
mock_mkdir.assert_called_once_with(parents=True, exist_ok=True)
|
|
|
|
@patch("utils.persistance.get_session_id", return_value="sess-xyz")
|
|
@patch("utils.persistance.os.getenv", return_value="custom_statut.json")
|
|
@patch("utils.persistance.Path.mkdir")
|
|
def test_utilise_nom_fichier_personnalise(self, mock_mkdir, mock_getenv, mock_get_sid):
|
|
"""Test avec un nom de fichier de statut personnalise via variable d'environnement."""
|
|
import utils.persistance as mod
|
|
|
|
update_session_paths()
|
|
|
|
assert mod.SAVE_STATUT == "custom_statut.json"
|
|
assert mod.SAVE_STATUT_PATH == Path("tmp/sessions/sess-xyz/custom_statut.json")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests pour _maj_champ
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestMajChamp:
|
|
"""Tests pour la fonction _maj_champ (mise a jour d'un champ JSON)."""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_creation_fichier_inexistant(self, mock_st, tmp_path):
|
|
"""Test la creation d'un nouveau fichier JSON si inexistant."""
|
|
fichier = tmp_path / "nouveau.json"
|
|
|
|
resultat = _maj_champ(fichier, "cle_simple", "valeur")
|
|
|
|
assert resultat is True
|
|
assert fichier.exists()
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu == {"cle_simple": "valeur"}
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_mise_a_jour_fichier_existant(self, mock_st, tmp_path):
|
|
"""Test la mise a jour d'un champ dans un fichier existant."""
|
|
fichier = _creer_fichier_json(tmp_path / "existant.json", {"ancien": "données"})
|
|
|
|
resultat = _maj_champ(fichier, "nouveau", "ajouté")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["ancien"] == "données"
|
|
assert contenu["nouveau"] == "ajouté"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_cle_imbriquee_avec_points(self, mock_st, tmp_path):
|
|
"""Test l'insertion d'une valeur via une cle hierarchique 'a.b.c'."""
|
|
fichier = tmp_path / "imbrique.json"
|
|
|
|
resultat = _maj_champ(fichier, "niveau1.niveau2.niveau3", "profonde")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["niveau1"]["niveau2"]["niveau3"] == "profonde"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_cle_imbriquee_fichier_existant(self, mock_st, tmp_path):
|
|
"""Test l'ajout d'une cle imbriquee dans un fichier existant."""
|
|
fichier = _creer_fichier_json(
|
|
tmp_path / "existant2.json",
|
|
{"config": {"theme": "sombre"}}
|
|
)
|
|
|
|
resultat = _maj_champ(fichier, "config.langue", "fr")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["config"]["theme"] == "sombre"
|
|
assert contenu["config"]["langue"] == "fr"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_ecrasement_valeur_existante(self, mock_st, tmp_path):
|
|
"""Test que la mise a jour ecrase la valeur existante."""
|
|
fichier = _creer_fichier_json(tmp_path / "ecrase.json", {"cle": "ancienne"})
|
|
|
|
resultat = _maj_champ(fichier, "cle", "nouvelle")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["cle"] == "nouvelle"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_serialisation_date(self, mock_st, tmp_path):
|
|
"""Test que les objets date sont serialises en format ISO."""
|
|
fichier = tmp_path / "date.json"
|
|
date_test = date(2025, 6, 15)
|
|
|
|
resultat = _maj_champ(fichier, "derniere_mise_a_jour", date_test)
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["derniere_mise_a_jour"] == "2025-06-15"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_contenu_vide_par_defaut(self, mock_st, tmp_path):
|
|
"""Test que le contenu par defaut est une chaine vide."""
|
|
fichier = tmp_path / "vide.json"
|
|
|
|
resultat = _maj_champ(fichier, "statut")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["statut"] == ""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_contenu_numerique(self, mock_st, tmp_path):
|
|
"""Test avec une valeur numerique."""
|
|
fichier = tmp_path / "num.json"
|
|
|
|
resultat = _maj_champ(fichier, "score", 42)
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["score"] == 42
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_contenu_liste(self, mock_st, tmp_path):
|
|
"""Test avec une valeur de type liste."""
|
|
fichier = tmp_path / "liste.json"
|
|
|
|
resultat = _maj_champ(fichier, "elements", ["a", "b", "c"])
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["elements"] == ["a", "b", "c"]
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_contenu_boolean(self, mock_st, tmp_path):
|
|
"""Test avec une valeur booleenne."""
|
|
fichier = tmp_path / "bool.json"
|
|
|
|
resultat = _maj_champ(fichier, "actif", True)
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["actif"] is True
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_fichier_json_corrompu(self, mock_st, tmp_path):
|
|
"""Test la gestion d'un fichier JSON corrompu (syntaxe invalide)."""
|
|
fichier = tmp_path / "corrompu.json"
|
|
fichier.write_text("{ceci n'est pas du json valide", encoding="utf-8")
|
|
|
|
resultat = _maj_champ(fichier, "cle", "valeur")
|
|
|
|
assert resultat is False
|
|
mock_st.error.assert_called_once()
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_erreur_ecriture(self, mock_st, tmp_path):
|
|
"""Test la gestion d'erreur lors de l'ecriture du fichier."""
|
|
fichier = tmp_path / "readonly.json"
|
|
|
|
# On patch l'ouverture en ecriture pour lever une exception
|
|
with patch.object(Path, "open", side_effect=PermissionError("Permission denied")), \
|
|
patch.object(Path, "exists", return_value=False):
|
|
resultat = _maj_champ(fichier, "cle", "valeur")
|
|
|
|
assert resultat is False
|
|
mock_st.error.assert_called_once()
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_valeur_none(self, mock_st, tmp_path):
|
|
"""Test avec une valeur None."""
|
|
fichier = tmp_path / "none.json"
|
|
|
|
resultat = _maj_champ(fichier, "cle", None)
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["cle"] is None
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_caracteres_unicode(self, mock_st, tmp_path):
|
|
"""Test avec des caracteres speciaux et accents."""
|
|
fichier = tmp_path / "unicode.json"
|
|
|
|
resultat = _maj_champ(fichier, "texte", "Ceci est un texte avec des accents: eacu")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert "accents" in contenu["texte"]
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_cle_profonde_trois_niveaux(self, mock_st, tmp_path):
|
|
"""Test l'insertion sur une structure existante profonde."""
|
|
fichier = _creer_fichier_json(
|
|
tmp_path / "profond.json",
|
|
{"a": {"b": {"c": "existant"}}}
|
|
)
|
|
|
|
resultat = _maj_champ(fichier, "a.b.d", "nouveau")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu["a"]["b"]["c"] == "existant"
|
|
assert contenu["a"]["b"]["d"] == "nouveau"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests pour _get_champ
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGetChamp:
|
|
"""Tests pour la fonction _get_champ (lecture d'un champ JSON)."""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_lecture_cle_simple(self, mock_st, tmp_path):
|
|
"""Test la lecture d'une cle de premier niveau."""
|
|
fichier = _creer_fichier_json(tmp_path / "simple.json", {"nom": "FabNum"})
|
|
|
|
resultat = _get_champ(fichier, "nom")
|
|
|
|
assert resultat == "FabNum"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_lecture_cle_imbriquee(self, mock_st, tmp_path):
|
|
"""Test la lecture d'une cle hierarchique 'a.b.c'."""
|
|
fichier = _creer_fichier_json(
|
|
tmp_path / "imbrique.json",
|
|
{"config": {"affichage": {"theme": "clair"}}}
|
|
)
|
|
|
|
resultat = _get_champ(fichier, "config.affichage.theme")
|
|
|
|
assert resultat == "clair"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_cle_inexistante_retourne_vide(self, mock_st, tmp_path):
|
|
"""Test qu'une cle inexistante retourne une chaine vide."""
|
|
fichier = _creer_fichier_json(tmp_path / "data.json", {"a": 1})
|
|
|
|
resultat = _get_champ(fichier, "cle_absente")
|
|
|
|
assert resultat == ""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_cle_imbriquee_inexistante(self, mock_st, tmp_path):
|
|
"""Test qu'une cle imbriquee manquante retourne une chaine vide."""
|
|
fichier = _creer_fichier_json(tmp_path / "data2.json", {"a": {"b": 1}})
|
|
|
|
resultat = _get_champ(fichier, "a.c.d")
|
|
|
|
assert resultat == ""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_fichier_inexistant_retourne_vide(self, mock_st, tmp_path):
|
|
"""Test qu'un fichier inexistant retourne une chaine vide."""
|
|
fichier = tmp_path / "inexistant.json"
|
|
|
|
resultat = _get_champ(fichier, "cle")
|
|
|
|
assert resultat == ""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_lecture_valeur_numerique(self, mock_st, tmp_path):
|
|
"""Test la lecture d'une valeur numerique."""
|
|
fichier = _creer_fichier_json(tmp_path / "num.json", {"score": 95})
|
|
|
|
resultat = _get_champ(fichier, "score")
|
|
|
|
assert resultat == 95
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_lecture_valeur_liste(self, mock_st, tmp_path):
|
|
"""Test la lecture d'une valeur de type liste."""
|
|
fichier = _creer_fichier_json(
|
|
tmp_path / "liste.json", {"items": ["x", "y", "z"]}
|
|
)
|
|
|
|
resultat = _get_champ(fichier, "items")
|
|
|
|
assert resultat == ["x", "y", "z"]
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_lecture_valeur_dict(self, mock_st, tmp_path):
|
|
"""Test la lecture d'un sous-dictionnaire entier."""
|
|
fichier = _creer_fichier_json(
|
|
tmp_path / "dict.json", {"meta": {"auteur": "test", "version": 1}}
|
|
)
|
|
|
|
resultat = _get_champ(fichier, "meta")
|
|
|
|
assert resultat == {"auteur": "test", "version": 1}
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_lecture_valeur_booleenne(self, mock_st, tmp_path):
|
|
"""Test la lecture d'une valeur booleenne."""
|
|
fichier = _creer_fichier_json(tmp_path / "bool.json", {"actif": False})
|
|
|
|
resultat = _get_champ(fichier, "actif")
|
|
|
|
assert resultat is False
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_fichier_json_corrompu(self, mock_st, tmp_path):
|
|
"""Test la gestion d'un fichier JSON corrompu."""
|
|
fichier = tmp_path / "corrompu.json"
|
|
fichier.write_text("pas du json!!!", encoding="utf-8")
|
|
|
|
resultat = _get_champ(fichier, "cle")
|
|
|
|
assert resultat == ""
|
|
mock_st.error.assert_called_once()
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_fichier_json_double_encode(self, mock_st, tmp_path):
|
|
"""Test la lecture d'un JSON double-encode (chaine JSON dans une chaine)."""
|
|
# Un fichier dont le contenu est un JSON valide encode en chaine
|
|
contenu_interne = json.dumps({"cle": "valeur"})
|
|
fichier = tmp_path / "double.json"
|
|
fichier.write_text(json.dumps(contenu_interne), encoding="utf-8")
|
|
|
|
resultat = _get_champ(fichier, "cle")
|
|
|
|
assert resultat == "valeur"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_fichier_json_double_encode_invalide(self, mock_st, tmp_path):
|
|
"""Test avec un fichier contenant une chaine qui n'est pas du JSON valide."""
|
|
fichier = tmp_path / "double_invalide.json"
|
|
# Ecrire une chaine JSON (pas un dict) dont le contenu n'est pas parseable en JSON
|
|
fichier.write_text(json.dumps("ceci n'est pas du json"), encoding="utf-8")
|
|
|
|
resultat = _get_champ(fichier, "cle")
|
|
|
|
assert resultat == ""
|
|
mock_st.error.assert_called_once()
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_fichier_contenu_non_dict(self, mock_st, tmp_path):
|
|
"""Test avec un fichier JSON contenant une liste au lieu d'un dict."""
|
|
fichier = tmp_path / "liste_racine.json"
|
|
fichier.write_text(json.dumps([1, 2, 3]), encoding="utf-8")
|
|
|
|
resultat = _get_champ(fichier, "cle")
|
|
|
|
assert resultat == ""
|
|
mock_st.error.assert_called_once()
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_cle_un_seul_niveau(self, mock_st, tmp_path):
|
|
"""Test avec une cle sans point (un seul niveau)."""
|
|
fichier = _creer_fichier_json(tmp_path / "flat.json", {"racine": "val"})
|
|
|
|
resultat = _get_champ(fichier, "racine")
|
|
|
|
assert resultat == "val"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_cle_intermediaire_non_dict(self, mock_st, tmp_path):
|
|
"""Test avec une cle intermediaire qui n'est pas un dictionnaire."""
|
|
fichier = _creer_fichier_json(
|
|
tmp_path / "type_err.json", {"a": "chaine_pas_dict"}
|
|
)
|
|
|
|
resultat = _get_champ(fichier, "a.b")
|
|
|
|
assert resultat == ""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests pour _supprime_champ
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestSupprimeChamp:
|
|
"""Tests pour la fonction _supprime_champ (suppression d'un champ JSON)."""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_suppression_cle_simple(self, mock_st, tmp_path):
|
|
"""Test la suppression d'une cle de premier niveau."""
|
|
fichier = _creer_fichier_json(
|
|
tmp_path / "supp.json", {"a": 1, "b": 2}
|
|
)
|
|
|
|
resultat = _supprime_champ(fichier, "a")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert "a" not in contenu
|
|
assert contenu["b"] == 2
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_suppression_cle_imbriquee(self, mock_st, tmp_path):
|
|
"""Test la suppression d'une cle imbriquee 'a.b.c'."""
|
|
fichier = _creer_fichier_json(
|
|
tmp_path / "supp_imbrique.json",
|
|
{"x": {"y": {"z": "a_supprimer", "w": "garder"}}}
|
|
)
|
|
|
|
resultat = _supprime_champ(fichier, "x.y.z")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert "z" not in contenu["x"]["y"]
|
|
assert contenu["x"]["y"]["w"] == "garder"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_suppression_cle_inexistante(self, mock_st, tmp_path):
|
|
"""Test la suppression d'une cle qui n'existe pas."""
|
|
fichier = _creer_fichier_json(tmp_path / "data.json", {"a": 1})
|
|
|
|
resultat = _supprime_champ(fichier, "cle_absente")
|
|
|
|
# La fonction retourne True meme si la cle n'existait pas
|
|
assert resultat is True
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_fichier_json_corrompu(self, mock_st, tmp_path):
|
|
"""Test la gestion d'un fichier JSON corrompu."""
|
|
fichier = tmp_path / "corrompu.json"
|
|
fichier.write_text("json invalide!!!", encoding="utf-8")
|
|
|
|
resultat = _supprime_champ(fichier, "cle")
|
|
|
|
assert resultat is False
|
|
mock_st.error.assert_called_once()
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_suppression_preserv_structure(self, mock_st, tmp_path):
|
|
"""Test que la structure restante est preservee apres suppression."""
|
|
fichier = _creer_fichier_json(
|
|
tmp_path / "preserve.json",
|
|
{"config": {"a": 1, "b": 2}, "data": {"x": 10}}
|
|
)
|
|
|
|
resultat = _supprime_champ(fichier, "config.a")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu == {"config": {"b": 2}, "data": {"x": 10}}
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_chemin_invalide_intermediaire(self, mock_st, tmp_path):
|
|
"""Test avec un chemin dont un element intermediaire n'est pas un dict."""
|
|
fichier = _creer_fichier_json(
|
|
tmp_path / "invalide.json",
|
|
{"a": "chaine"}
|
|
)
|
|
|
|
# La fonction ne crash pas, le supprimer_cle_profonde retourne False
|
|
resultat = _supprime_champ(fichier, "a.b.c")
|
|
|
|
assert resultat is True # La fonction retourne True meme si rien n'a ete supprime
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests pour les wrappers (maj_champ_statut, get_champ_statut, supprime_champ_statut)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestWrappersStatut:
|
|
"""Tests pour les wrappers qui utilisent SAVE_STATUT_PATH."""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_maj_champ_statut(self, mock_st, tmp_path):
|
|
"""Test que maj_champ_statut delegue a _maj_champ avec le bon fichier."""
|
|
import utils.persistance as mod
|
|
|
|
fichier_statut = tmp_path / "statut.json"
|
|
mod.SAVE_STATUT_PATH = fichier_statut
|
|
|
|
from utils.persistance import maj_champ_statut
|
|
|
|
resultat = maj_champ_statut("test.cle", "valeur_test")
|
|
|
|
assert resultat is True
|
|
contenu = json.loads(fichier_statut.read_text(encoding="utf-8"))
|
|
assert contenu["test"]["cle"] == "valeur_test"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_get_champ_statut(self, mock_st, tmp_path):
|
|
"""Test que get_champ_statut delegue a _get_champ avec le bon fichier."""
|
|
import utils.persistance as mod
|
|
|
|
fichier_statut = _creer_fichier_json(
|
|
tmp_path / "statut.json", {"resultat": {"score": 88}}
|
|
)
|
|
mod.SAVE_STATUT_PATH = fichier_statut
|
|
|
|
from utils.persistance import get_champ_statut
|
|
|
|
resultat = get_champ_statut("resultat.score")
|
|
|
|
assert resultat == 88
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_supprime_champ_statut(self, mock_st, tmp_path):
|
|
"""Test que supprime_champ_statut delegue a _supprime_champ avec le bon fichier."""
|
|
import utils.persistance as mod
|
|
|
|
fichier_statut = _creer_fichier_json(
|
|
tmp_path / "statut.json", {"a": 1, "b": 2}
|
|
)
|
|
mod.SAVE_STATUT_PATH = fichier_statut
|
|
|
|
from utils.persistance import supprime_champ_statut
|
|
|
|
supprime_champ_statut("a")
|
|
|
|
contenu = json.loads(fichier_statut.read_text(encoding="utf-8"))
|
|
assert "a" not in contenu
|
|
assert contenu["b"] == 2
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_maj_puis_get_champ_statut(self, mock_st, tmp_path):
|
|
"""Test le cycle complet: ecriture puis relecture."""
|
|
import utils.persistance as mod
|
|
|
|
fichier_statut = tmp_path / "statut_cycle.json"
|
|
mod.SAVE_STATUT_PATH = fichier_statut
|
|
|
|
from utils.persistance import get_champ_statut, maj_champ_statut
|
|
|
|
maj_champ_statut("analyse.resultat", "termine")
|
|
resultat = get_champ_statut("analyse.resultat")
|
|
|
|
assert resultat == "termine"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_maj_puis_supprime_puis_get(self, mock_st, tmp_path):
|
|
"""Test le cycle: ecriture, suppression, relecture."""
|
|
import utils.persistance as mod
|
|
|
|
fichier_statut = tmp_path / "statut_cycle2.json"
|
|
mod.SAVE_STATUT_PATH = fichier_statut
|
|
|
|
from utils.persistance import (
|
|
get_champ_statut,
|
|
maj_champ_statut,
|
|
supprime_champ_statut,
|
|
)
|
|
|
|
maj_champ_statut("temporaire", "a_supprimer")
|
|
supprime_champ_statut("temporaire")
|
|
resultat = get_champ_statut("temporaire")
|
|
|
|
assert resultat == ""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests pour get_full_structure
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGetFullStructure:
|
|
"""Tests pour la fonction get_full_structure."""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_lecture_structure_complete(self, mock_st, tmp_path):
|
|
"""Test la lecture de la structure JSON complete."""
|
|
import utils.persistance as mod
|
|
|
|
structure = {"config": {"theme": "sombre"}, "data": [1, 2, 3]}
|
|
fichier = _creer_fichier_json(tmp_path / "full.json", structure)
|
|
mod.SAVE_STATUT_PATH = fichier
|
|
|
|
resultat = get_full_structure()
|
|
|
|
assert resultat == structure
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_fichier_inexistant_retourne_none(self, mock_st, tmp_path):
|
|
"""Test qu'un fichier inexistant retourne None."""
|
|
import utils.persistance as mod
|
|
|
|
mod.SAVE_STATUT_PATH = tmp_path / "inexistant.json"
|
|
|
|
resultat = get_full_structure()
|
|
|
|
assert resultat is None
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_fichier_json_corrompu(self, mock_st, tmp_path):
|
|
"""Test la gestion d'un fichier JSON corrompu."""
|
|
import utils.persistance as mod
|
|
|
|
fichier = tmp_path / "corrompu.json"
|
|
fichier.write_text("{json cassé", encoding="utf-8")
|
|
mod.SAVE_STATUT_PATH = fichier
|
|
|
|
resultat = get_full_structure()
|
|
|
|
assert resultat is None
|
|
mock_st.error.assert_called_once()
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_fichier_vide(self, mock_st, tmp_path):
|
|
"""Test la gestion d'un fichier JSON vide."""
|
|
import utils.persistance as mod
|
|
|
|
fichier = tmp_path / "vide.json"
|
|
fichier.write_text("", encoding="utf-8")
|
|
mod.SAVE_STATUT_PATH = fichier
|
|
|
|
resultat = get_full_structure()
|
|
|
|
assert resultat is None
|
|
mock_st.error.assert_called_once()
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_structure_complexe(self, mock_st, tmp_path):
|
|
"""Test avec une structure JSON riche et imbriquee."""
|
|
import utils.persistance as mod
|
|
|
|
structure = {
|
|
"session": {
|
|
"id": "abc-123",
|
|
"analyse": {
|
|
"graphe": "charge",
|
|
"indicateurs": [
|
|
{"nom": "IHH", "valeur": 0.65},
|
|
{"nom": "IVC", "valeur": 0.32},
|
|
],
|
|
},
|
|
},
|
|
"metadata": {"version": 2, "date": "2025-06-15"},
|
|
}
|
|
fichier = _creer_fichier_json(tmp_path / "complexe.json", structure)
|
|
mod.SAVE_STATUT_PATH = fichier
|
|
|
|
resultat = get_full_structure()
|
|
|
|
assert resultat == structure
|
|
assert resultat["session"]["analyse"]["indicateurs"][0]["nom"] == "IHH"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests d'integration (cycle complet)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestIntegrationCycleComplet:
|
|
"""Tests d'integration verifiant les operations de bout en bout."""
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_cycle_creation_lecture_suppression(self, mock_st, tmp_path):
|
|
"""Test un cycle complet: creation, lecture, modification, suppression."""
|
|
fichier = tmp_path / "integration.json"
|
|
|
|
# 1. Creer un champ
|
|
assert _maj_champ(fichier, "projet.nom", "FabNum") is True
|
|
|
|
# 2. Lire le champ
|
|
assert _get_champ(fichier, "projet.nom") == "FabNum"
|
|
|
|
# 3. Ajouter un autre champ
|
|
assert _maj_champ(fichier, "projet.version", "1.0") is True
|
|
|
|
# 4. Verifier les deux champs
|
|
assert _get_champ(fichier, "projet.nom") == "FabNum"
|
|
assert _get_champ(fichier, "projet.version") == "1.0"
|
|
|
|
# 5. Supprimer un champ
|
|
assert _supprime_champ(fichier, "projet.version") is True
|
|
|
|
# 6. Verifier que le champ est bien supprime
|
|
assert _get_champ(fichier, "projet.version") == ""
|
|
assert _get_champ(fichier, "projet.nom") == "FabNum"
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_multiple_cles_imbriquees(self, mock_st, tmp_path):
|
|
"""Test avec plusieurs cles imbriquees ajoutees incrementalement."""
|
|
fichier = tmp_path / "multi.json"
|
|
|
|
_maj_champ(fichier, "a.b.c", "v1")
|
|
_maj_champ(fichier, "a.b.d", "v2")
|
|
_maj_champ(fichier, "a.e", "v3")
|
|
_maj_champ(fichier, "f", "v4")
|
|
|
|
assert _get_champ(fichier, "a.b.c") == "v1"
|
|
assert _get_champ(fichier, "a.b.d") == "v2"
|
|
assert _get_champ(fichier, "a.e") == "v3"
|
|
assert _get_champ(fichier, "f") == "v4"
|
|
|
|
contenu = json.loads(fichier.read_text(encoding="utf-8"))
|
|
assert contenu == {
|
|
"a": {"b": {"c": "v1", "d": "v2"}, "e": "v3"},
|
|
"f": "v4",
|
|
}
|
|
|
|
@patch("utils.persistance.st")
|
|
def test_serialisation_date_puis_relecture(self, mock_st, tmp_path):
|
|
"""Test que la date serialisee est relue comme chaine ISO."""
|
|
fichier = tmp_path / "date_cycle.json"
|
|
|
|
_maj_champ(fichier, "date_debut", date(2025, 1, 1))
|
|
|
|
resultat = _get_champ(fichier, "date_debut")
|
|
|
|
assert resultat == "2025-01-01"
|