Code/tests/unit/test_persistance.py
Stéphan Peccini 8e2556c2b0
test(unit): +381 tests unitaires — couverture 16%→35%
- 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>
2026-03-02 11:52:21 +01:00

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"