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>
79 lines
2.8 KiB
Python
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
|