feat(gui): add GUI V6 G1 foundation (license client/store, shell, About tab)
Socle de la refonte GUI V6 (couche présentation uniquement, aucune logique de détection) : - license_store: stockage licence hors dépôt (%LOCALAPPDATA%/Aivanov | XDG), read/write atomique/delete, ne journalise aucun token - license_client: LicenseStatus + activate/check/local_status, session HTTP injectable, serveur indisponible géré sans crash, aucune clé privée - theme: 4 thèmes + couleurs de statut licence - app + tab_about: shell customtkinter minimal (header, bandeau licence, 3 onglets), onglet À propos étoffé - Pseudonymisation_Gui_V6.py: point d'entrée + --self-test (exit 0 sans fenêtre) - requirements.txt: customtkinter==5.2.2 Tests: 20 nouveaux (store sur vrais fichiers, client sur session injectée). Suite tests/unit: 167 passed, 0 régression. V5/moteur/managers/specs intacts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
91
tests/unit/test_gui_v6_license_store.py
Normal file
91
tests/unit/test_gui_v6_license_store.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""Tests du stockage local de licence (vrais fichiers, aucun mock).
|
||||
|
||||
Couvre : résolution du chemin par plateforme, round-trip save/load, création
|
||||
du répertoire parent, suppression, robustesse au JSON corrompu.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from gui_v6.license_store import LicenseStore, default_license_path
|
||||
|
||||
|
||||
def test_default_path_windows(monkeypatch):
|
||||
monkeypatch.setattr("sys.platform", "win32")
|
||||
monkeypatch.setenv("LOCALAPPDATA", r"C:\Users\dom\AppData\Local")
|
||||
path = default_license_path()
|
||||
parts = path.as_posix()
|
||||
assert parts.endswith("Aivanov/Anonymisation/license.json")
|
||||
|
||||
|
||||
def test_default_path_linux(monkeypatch):
|
||||
monkeypatch.setattr("sys.platform", "linux")
|
||||
monkeypatch.delenv("XDG_DATA_HOME", raising=False)
|
||||
monkeypatch.setattr(Path, "home", classmethod(lambda cls: Path("/home/tester")))
|
||||
path = default_license_path()
|
||||
assert path == Path("/home/tester/.local/share/aivanov/anonymisation/license.json")
|
||||
|
||||
|
||||
def test_default_path_linux_respects_xdg(monkeypatch):
|
||||
monkeypatch.setattr("sys.platform", "linux")
|
||||
monkeypatch.setenv("XDG_DATA_HOME", "/custom/data")
|
||||
path = default_license_path()
|
||||
assert path == Path("/custom/data/aivanov/anonymisation/license.json")
|
||||
|
||||
|
||||
def test_save_creates_parent_dir_and_roundtrips(tmp_path):
|
||||
target = tmp_path / "nested" / "dir" / "license.json"
|
||||
store = LicenseStore(target)
|
||||
assert not store.exists()
|
||||
|
||||
payload = {"status": "active", "license_ref": "LIC-123", "expires_at": "2027-01-01"}
|
||||
store.save(payload)
|
||||
|
||||
assert target.is_file()
|
||||
assert store.exists()
|
||||
assert store.load() == payload
|
||||
# Vérifie aussi le contenu sur disque (vrai fichier JSON).
|
||||
on_disk = json.loads(target.read_text(encoding="utf-8"))
|
||||
assert on_disk["license_ref"] == "LIC-123"
|
||||
|
||||
|
||||
def test_load_absent_returns_none(tmp_path):
|
||||
store = LicenseStore(tmp_path / "absent.json")
|
||||
assert store.load() is None
|
||||
|
||||
|
||||
def test_load_corrupted_returns_none(tmp_path):
|
||||
target = tmp_path / "license.json"
|
||||
target.write_text("{ this is not valid json", encoding="utf-8")
|
||||
store = LicenseStore(target)
|
||||
assert store.load() is None
|
||||
|
||||
|
||||
def test_load_non_object_returns_none(tmp_path):
|
||||
target = tmp_path / "license.json"
|
||||
target.write_text("[1, 2, 3]", encoding="utf-8")
|
||||
store = LicenseStore(target)
|
||||
assert store.load() is None
|
||||
|
||||
|
||||
def test_delete(tmp_path):
|
||||
target = tmp_path / "license.json"
|
||||
store = LicenseStore(target)
|
||||
store.save({"status": "active"})
|
||||
assert store.delete() is True
|
||||
assert not target.exists()
|
||||
# Suppression idempotente : un second delete ne lève pas.
|
||||
assert store.delete() is False
|
||||
|
||||
|
||||
def test_save_overwrites(tmp_path):
|
||||
store = LicenseStore(tmp_path / "license.json")
|
||||
store.save({"status": "active", "v": 1})
|
||||
store.save({"status": "grace", "v": 2})
|
||||
loaded = store.load()
|
||||
assert loaded["status"] == "grace"
|
||||
assert loaded["v"] == 2
|
||||
Reference in New Issue
Block a user