Files
rpa_vision_v3/tests/unit/test_agent_logs_store.py
Dom a29b7a2f21 feat(server): store de logs clients par machine_id (push-log-DGX, brique 1)
AgentLogsStore : append/read JSONL rangés par machine_id (fichier par jour),
anti path-traversal sur machine_id (entrée réseau), purge_old rétention 30j
(garde-fou G4 Qwen). TDD 3/3 vert. Pas encore wired (endpoint = brique 2).

refs DETTE-020 DETTE-021

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:14:28 +02:00

79 lines
2.8 KiB
Python

"""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