Files
Adriano 1a7f670966 sei sorveglianti registrano i giri e stanno in monitor_health (Fixes #16)
src/live/giri.registra_giro: una riga jsonl {ts ms, campi} dopo una misura completata,
mai un'eccezione. edge_watch, fee_watch, scale_watch, vrp_f_watch, venue_news (cron_daily)
e venue_watch (cron_book, orario) scrivono data/<dir>/runs.jsonl; niente riga senza misura.
monitor_health: sei MonitorSpec (30h i giornalieri, 3h venue_watch), open_labeled=False.
Stessa riparazione di cc01 (#10, #12), generalizzata. Primo giro notturno: NUOVO, non allerta.
Test: incrocio P1 percorso/RUNS, misura -> una riga, non-misura -> nessuna. Suite: 1098 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 08:48:07 +00:00

70 lines
2.5 KiB
Python

"""registra_giro (src/live/giri.py, #16): il registro che i sorveglianti scrivono a ogni giro.
Il contratto e' quello di cc01_regime_watch, generalizzato: appende `{"ts": <ms>, **campi}` come
riga jsonl e non solleva MAI — un registro che fallisce non deve buttare via una misura gia' fatta.
"""
from __future__ import annotations
import json
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from src.live.giri import registra_giro # noqa: E402
def test_scrive_una_riga_con_ts_in_millisecondi(tmp_path):
p = tmp_path / "sub" / "runs.jsonl"
prima = time.time() * 1000
assert registra_giro(p, stato="OK", n=3) is True
riga = json.loads(p.read_text().strip())
assert abs(riga["ts"] - prima) < 5_000
assert isinstance(riga["ts"], int)
assert riga["stato"] == "OK" and riga["n"] == 3
def test_crea_la_cartella_se_manca(tmp_path):
p = tmp_path / "non-esiste-ancora" / "runs.jsonl"
assert not p.parent.exists()
registra_giro(p, x=1)
assert p.exists()
def test_appende_senza_troncare(tmp_path):
p = tmp_path / "runs.jsonl"
registra_giro(p, giro=1)
registra_giro(p, giro=2)
righe = [json.loads(r) for r in p.read_text().splitlines()]
assert [r["giro"] for r in righe] == [1, 2]
def test_default_str_copre_i_valori_non_serializzabili(tmp_path):
"""json.dumps(default=str): un valore che non e' nativamente JSON (es. un Path, o un
dataclass senza __dict__ diretto) non deve far fallire l'append — diventa la sua str()."""
p = tmp_path / "runs.jsonl"
assert registra_giro(p, percorso=Path("/tmp/x")) is True
riga = json.loads(p.read_text().strip())
assert riga["percorso"] == str(Path("/tmp/x"))
def test_oserror_ritorna_false_senza_sollevare(tmp_path, capsys):
"""Un percorso impossibile (qui: dentro un FILE, non una cartella) e' un OSError alla
mkdir/scrittura. Il chiamante non deve vedere un'eccezione: la misura e' gia' fatta."""
ostacolo = tmp_path / "non-e-una-cartella"
ostacolo.write_text("sono un file")
p = ostacolo / "runs.jsonl"
assert registra_giro(p, stato="OK") is False
err = capsys.readouterr().err
assert "registra_giro" in err and str(p) in err
def test_un_valore_non_serializzabile_diventa_stringa_non_solleva(tmp_path):
p = tmp_path / "runs.jsonl"
oggetto = object()
assert registra_giro(p, oggetto=oggetto) is True
riga = json.loads(p.read_text().strip())
assert riga["oggetto"] == str(oggetto)