"""Stockage JSON : ecriture atomique, schema_version, migration, recuperation automatique depuis .bak quand le fichier principal est corrompu.""" import pathlib import sys sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent)) import json import tempfile from pathlib import Path from app.key_manager import KeyManager, compute_fingerprint from app.session_manager import Session, SessionStore from app.storage.atomic_file import atomic_write_text from app.storage.json_store import load_json_document, save_json_document tmp = Path(tempfile.mkdtemp()) print("=== 1. Ecriture atomique : pas de fichier .tmp residuel, contenu exact ===") target = tmp / "fichier.txt" atomic_write_text(target, "contenu") assert target.read_text(encoding="utf-8") == "contenu" assert not target.with_name("fichier.txt.tmp").exists() print(" OK") print() print("=== 2. load_json_document : valeurs par defaut si le fichier n'existe pas ===") missing = tmp / "absent.json" doc, warning = load_json_document(missing, default={"schema_version": 1, "items": []}, current_version=1) assert doc == {"schema_version": 1, "items": []}, doc assert warning is None print(" OK") print() print("=== 3. Migration : un fichier sans schema_version est promu a current_version ===") legacy = tmp / "legacy.json" legacy.write_text(json.dumps({"items": ["a", "b"]}), encoding="utf-8") def _v0_to_v1(document: dict) -> dict: document["items"] = [x.upper() for x in document.get("items", [])] document["schema_version"] = 1 return document doc, warning = load_json_document( legacy, default={"schema_version": 1, "items": []}, current_version=1, migrations={0: _v0_to_v1}, ) print(" document migre:", doc, "| warning:", warning) assert doc["schema_version"] == 1 assert doc["items"] == ["A", "B"], doc assert warning is None print(" OK") print() print("=== 4. save_json_document : cree un .bak de la version precedente ===") target2 = tmp / "doc.json" save_json_document(target2, {"schema_version": 1, "n": 1}) save_json_document(target2, {"schema_version": 1, "n": 2}) bak = target2.with_name("doc.json.bak") assert bak.exists() assert json.loads(bak.read_text(encoding="utf-8"))["n"] == 1 assert json.loads(target2.read_text(encoding="utf-8"))["n"] == 2 print(" .bak contient la version precedente, le fichier principal la derniere : OK") print() print("=== 5. Recuperation automatique si le fichier principal est corrompu ===") target2.write_text("{ceci n'est pas du json", encoding="utf-8") doc, warning = load_json_document(target2, default={"schema_version": 1, "n": 0}, current_version=1) print(" document recupere:", doc, "| warning:", warning) assert doc["n"] == 1, doc # revient a l'avant-derniere version connue (le .bak) assert warning is not None and "bak" in warning print(" OK") print() print("=== 6. SessionStore : fichier legacy sans schema_version reste chargeable ===") sessions_path = tmp / "sessions.json" sessions_path.write_text( json.dumps({"sessions": [{"id": "s1", "name": "srv1", "host": "10.0.0.1"}]}), encoding="utf-8", ) store = SessionStore(path=sessions_path) assert [s.name for s in store.sessions] == ["srv1"] store.add(Session(name="srv2", host="10.0.0.2")) on_disk = json.loads(sessions_path.read_text(encoding="utf-8")) print(" schema_version apres sauvegarde:", on_disk.get("schema_version")) assert on_disk.get("schema_version") == 1 assert sessions_path.with_name("sessions.json.bak").exists() print(" OK") print() print("=== 7. KeyManager : une empreinte au mauvais format est corrigee au chargement ===") keys_path = tmp / "keys.json" # cle publique valide obtenue via une vraie generation, pour ne pas fabriquer un # base64 a la main _seed_km = KeyManager(registry_path=tmp / "seed_keys.json") pub_line = _seed_km.generate_key("seed", save_dir=tmp / "seed").public_key_text bogus_fp = pub_line.split()[1][:16] # l'ancien "fingerprint" errone : un bout de base64 keys_path.write_text(json.dumps({ "keys": [{ "id": "k1", "name": "importee", "key_type": "ed25519", "private_path": "", "public_path": "", "fingerprint": bogus_fp, "public_key_text": pub_line, }], }), encoding="utf-8") km = KeyManager(registry_path=keys_path) entry = km.keys[0] expected = compute_fingerprint(pub_line) print(" ancienne empreinte (tronquee):", bogus_fp) print(" empreinte corrigee: ", entry.fingerprint) assert entry.fingerprint == expected assert entry.fingerprint.startswith("SHA256:") assert entry.fingerprint != bogus_fp print(" OK") print() print("STOCKAGE ATOMIQUE / SCHEMA / FINGERPRINT : TOUS LES TESTS OK")