import json from types import SimpleNamespace from gui_v6 import diagnostics def _doc(**kw): base = dict(ordinal=0, status="success", error_type=None, error_code=None, duration_ms=12) base.update(kw) return SimpleNamespace(**base) def test_new_run_id_is_hex(): rid = diagnostics.new_run_id() assert isinstance(rid, str) and len(rid) >= 16 def test_items_from_summary_whitelist_only(): summary = SimpleNamespace(documents=[ _doc(ordinal=0, status="success"), _doc(ordinal=1, status="failed", error_type="ValueError", error_code="processing_error"), ]) items = diagnostics.items_from_summary(summary) assert items[1]["error_type"] == "ValueError" assert set(items[0]) <= {"ordinal", "status", "error_type", "error_code", "duration_ms"} def test_build_payload_counts_and_no_pii_leak(): # On INJECTE de la PII via des clés interdites + un faux message d'erreur : raw_docs = [ {"ordinal": 0, "status": "success", "duration_ms": 5, "filename": "LETTRE Dupont 1980.pdf", "path": "/home/dom/secret.pdf"}, {"ordinal": 1, "status": "failed", "error_type": "ValueError", "error_code": "processing_error", "error_message": "patient Dupont Jean"}, ] payload = diagnostics.build_diagnostics_payload( run_id="r" * 16, app_name="gui_v6", app_version="6.0.0-g1", license_ref="LIC-1", machine_id="m" * 12, duration_ms=999, items=raw_docs, ) assert payload["document_count"] == 2 assert payload["succeeded_count"] == 1 and payload["failed_count"] == 1 blob = json.dumps(payload).lower() for forbidden in ("filename", "path", "secret", "dupont", "lettre", "error_message", "patient"): assert forbidden not in blob, f"fuite RGPD : {forbidden}" for item in payload["items"]: assert set(item) <= {"ordinal", "status", "error_type", "error_code", "duration_ms"} class _FakeResp: def __init__(self, status_code): self.status_code = status_code class _FakeSession: def __init__(self, status_code=200, raise_exc=None): self.status_code = status_code self.raise_exc = raise_exc self.calls = [] def post(self, url, json=None, timeout=None): self.calls.append((url, json, timeout)) if self.raise_exc: raise self.raise_exc return _FakeResp(self.status_code) def test_client_report_ok_on_2xx(): sess = _FakeSession(status_code=200) client = diagnostics.DiagnosticsClient("https://app.aivanov.eu/", session=sess) assert client.report({"run_id": "r"}) is True assert sess.calls[0][0] == "https://app.aivanov.eu/api/v1/diagnostics/report" def test_client_report_false_on_network_error_without_raising(): sess = _FakeSession(raise_exc=RuntimeError("no network")) client = diagnostics.DiagnosticsClient("https://app.aivanov.eu", session=sess) assert client.report({"run_id": "r"}) is False # ne lève pas def test_report_run_diagnostics_no_send_without_license(tmp_path): sess = _FakeSession() ok = diagnostics.report_run_diagnostics( SimpleNamespace(documents=[]), base_url="https://app.aivanov.eu", license_ref=None, machine_id="m" * 12, session=sess, spool_path=tmp_path / "spool.jsonl", ) assert ok is False and sess.calls == [] def test_report_run_diagnostics_network_down_spools(tmp_path): sess = _FakeSession(raise_exc=RuntimeError("down")) spool = tmp_path / "spool.jsonl" summary = SimpleNamespace(documents=[_doc(ordinal=0, status="failed", error_type="ValueError", error_code="processing_error")]) ok = diagnostics.report_run_diagnostics( summary, base_url="https://app.aivanov.eu", license_ref="LIC-1", machine_id="m" * 12, session=sess, spool_path=spool, ) assert ok is False and spool.exists() line = json.loads(spool.read_text(encoding="utf-8").splitlines()[0]) assert line["failed_count"] == 1 def test_flush_spool_sends_and_clears(tmp_path): spool = tmp_path / "spool.jsonl" diagnostics.spool_payload(spool, {"run_id": "r1"}) diagnostics.spool_payload(spool, {"run_id": "r2"}) sent = diagnostics.flush_spool(spool, diagnostics.DiagnosticsClient( "https://app.aivanov.eu", session=_FakeSession(status_code=200))) assert sent == 2 and not spool.exists()