"""Tests unitaires du store de logs poussés par les clients Léa (push-log-DGX). Le store persiste les logs reçus du client, rangés par `machine_id`, pour consultation au dashboard (diagnostic des postes sans AnyDesk). Stockage fichier (JSONL par machine_id), rétention configurable. Branche : feat/push-log-dgx — DETTE-020/021 (observabilité). """ from __future__ import annotations import sys from pathlib import Path # Racine projet pour les imports locaux (meme pattern que tests/integration) _ROOT = str(Path(__file__).resolve().parents[2]) if _ROOT not in sys.path: sys.path.insert(0, _ROOT) def test_append_then_read_roundtrip(tmp_path): """append() persiste un batch ; read() le restitue dans l'ordre.""" from agent_v0.server_v1.agent_logs_store import AgentLogsStore store = AgentLogsStore(base_dir=tmp_path / "agent_logs") entries = [ {"ts": "2026-06-26T16:00:00", "level": "INFO", "logger": "agent_v1.main", "message": "demarrage"}, {"ts": "2026-06-26T16:00:01", "level": "WARNING", "logger": "agent_v1.core.executor", "message": "popup detectee"}, ] store.append("lea-emilie-001", entries) got = store.read("lea-emilie-001") assert len(got) == 2 assert got[0]["message"] == "demarrage" assert got[0]["level"] == "INFO" assert got[1]["level"] == "WARNING" assert got[1]["logger"] == "agent_v1.core.executor" def test_machine_id_path_traversal_stays_within_base(tmp_path): """Un machine_id malveillant (entrée réseau) ne doit jamais écrire hors du base_dir.""" from agent_v0.server_v1.agent_logs_store import AgentLogsStore base = (tmp_path / "agent_logs").resolve() store = AgentLogsStore(base_dir=base) store.append("../../../evil", [{"message": "pwn"}]) written = list(base.rglob("*.jsonl")) assert written, "le batch doit être persisté SOUS base (pas d'évasion ni perte)" for p in written: assert base in p.resolve().parents, f"{p} échappe à {base}" # Aucune fuite hors de base assert not list(tmp_path.glob("evil*")) def test_purge_old_removes_files_older_than_retention(tmp_path): """purge_old() supprime les fichiers-jour antérieurs à la rétention (G4 Qwen).""" from datetime import datetime, timezone from agent_v0.server_v1.agent_logs_store import AgentLogsStore base = tmp_path / "agent_logs" store = AgentLogsStore(base_dir=base) mdir = base / "lea-001" mdir.mkdir(parents=True) (mdir / "2026-05-01.jsonl").write_text('{"message": "vieux"}\n', encoding="utf-8") (mdir / "2026-06-26.jsonl").write_text('{"message": "recent"}\n', encoding="utf-8") now = datetime(2026, 6, 26, tzinfo=timezone.utc) removed = store.purge_old(retention_days=30, now=now) remaining = {p.name for p in mdir.glob("*.jsonl")} assert remaining == {"2026-06-26.jsonl"} assert removed == 1