e82f2ee163
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
131 lines
5.9 KiB
Python
131 lines
5.9 KiB
Python
"""Test della diagnosi di guasto sul percorso Deribit (`src/live/venue_probe.py`) e delle due
|
|
riparazioni che ne dipendono: la finestra scoperta del disaster-SL e l'isolamento per asset.
|
|
|
|
NESSUN TEST TOCCA LA RETE: la sonda pubblica e' iniettabile: `diagnose(..., sonda=...)`.
|
|
|
|
Cosa difendono, in ordine di gravita':
|
|
1. **`naked`**: il disaster-SL cancellato e non ripiazzato deve avere uno stato PROPRIO, diverso
|
|
da `place-failed`. Sono due guasti diversi (P5) e solo uno lascia la posizione scoperta.
|
|
2. **isolamento per asset**: un guasto su BTC non deve impedire ad ETH di essere protetto.
|
|
3. **la diagnosi**: manutenzione, gateway e "non vedo" devono essere distinguibili (P4/P5).
|
|
4. **`atteso` e' sempre False** (#35 B11): il ramo che lo declassava (finestra di release del
|
|
martedi') e' stato rimosso perche' l'unico chiamante (cron :47) non ci cadeva mai dentro.
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from src.live.venue_probe import (V_GATEWAY, V_IGNOTO, V_MANUTENZIONE, V_VENUE_GIU, diagnose,
|
|
errori_dal_report)
|
|
|
|
MANUTENZIONE_GREZZA = ('HTTP 503 {"jsonrpc":"2.0","error":'
|
|
'{"message":"system_maintenance","code":11051}}')
|
|
|
|
|
|
# --- sonde finte -----------------------------------------------------------------------------
|
|
def _sonda_ok():
|
|
return True, 'HTTP 200 {"jsonrpc":"2.0","result":{"version":"1.2.26"}}'
|
|
|
|
|
|
def _sonda_manutenzione():
|
|
return False, MANUTENZIONE_GREZZA
|
|
|
|
|
|
def _sonda_rifiuta():
|
|
return False, "HTTP 500 <html>bad gateway</html>"
|
|
|
|
|
|
def _sonda_morta():
|
|
return None, "ConnectionError: dns"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. La diagnosi distingue i tre guasti che vogliono azioni diverse.
|
|
# ---------------------------------------------------------------------------
|
|
def test_venue_sano_significa_che_il_guasto_e_nostro():
|
|
"""Il caso che vale di piu': 4 dei 5 traceback in 63 giorni erano il NOSTRO gateway.
|
|
Se l'API pubblica risponde, il venue sta bene e il pezzo rotto e' quello riparabile."""
|
|
d = diagnose(["BTC: fallback close (HTTPError)"], sonda=_sonda_ok)
|
|
assert d.verdetto == V_GATEWAY
|
|
assert d.riparabile_da_noi is True
|
|
assert d.atteso is False # un gateway rotto non e' MAI atteso
|
|
assert d.gravita == "🛑"
|
|
assert "gateway" in d.perche.lower()
|
|
|
|
|
|
def test_manutenzione_riconosciuta_dalla_sonda():
|
|
d = diagnose(["BTC: fallback close (HTTPError)"], sonda=_sonda_manutenzione)
|
|
assert d.verdetto == V_MANUTENZIONE
|
|
assert d.riparabile_da_noi is False # aspettare, non riparare
|
|
|
|
|
|
def test_manutenzione_riconosciuta_dagli_errori_gia_raccolti_senza_sondare():
|
|
"""Il 18/08 il codice 11051 era GIA' negli errori dello strato book, nello stesso minuto, e
|
|
non arrivava a chi decideva la gravita'. Ora lo si guarda per primo: la sonda non parte
|
|
nemmeno (qui lo verifichiamo con una sonda che esploderebbe)."""
|
|
def _sonda_vietata():
|
|
raise AssertionError("la sonda non doveva partire: l'11051 era gia' negli errori")
|
|
|
|
d = diagnose(['OnMaintenance: deribit {"error":{"code":11051}}'], sonda=_sonda_vietata)
|
|
assert d.verdetto == V_MANUTENZIONE
|
|
|
|
|
|
def test_venue_giu_non_e_manutenzione():
|
|
d = diagnose([], sonda=_sonda_rifiuta)
|
|
assert d.verdetto == V_VENUE_GIU
|
|
assert d.atteso is False # non dichiara manutenzione -> resta rumoroso
|
|
|
|
|
|
def test_sonda_morta_e_ignoto_non_va_tutto_bene():
|
|
"""P5: «non vedo» non e' «va tutto bene». Il verdetto deve restare IGNOTO, mai GATEWAY."""
|
|
d = diagnose([], sonda=_sonda_morta)
|
|
assert d.verdetto == V_IGNOTO
|
|
assert d.riparabile_da_noi is False
|
|
assert d.atteso is False
|
|
|
|
|
|
def test_la_prova_viaggia_sempre_con_il_verdetto():
|
|
"""Un verdetto senza prova si legge come opinione. La riga deve portare il perche' (P4)."""
|
|
d = diagnose(["BTC: fallback close (HTTPError)"], sonda=_sonda_ok)
|
|
assert d.prova # non vuota
|
|
assert "—" in d.riga() and d.verdetto in d.riga()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. `atteso` e' sempre False (#35 B11) — nessun verdetto, manutenzione compresa, si declassa.
|
|
# ---------------------------------------------------------------------------
|
|
def test_manutenzione_non_si_declassa_piu_atteso_sempre_falso():
|
|
"""Il ramo che declassava (finestra di release del martedi') e' rimosso: una manutenzione
|
|
riconosciuta dalla sonda resta comunque `atteso is False` e alla gravita' piena."""
|
|
d = diagnose([], sonda=_sonda_manutenzione)
|
|
assert d.verdetto == V_MANUTENZIONE and d.atteso is False
|
|
assert d.gravita == "⚠️"
|
|
assert "ATTESO" not in d.riga()
|
|
|
|
|
|
def test_un_guasto_vero_non_e_mai_atteso():
|
|
d = diagnose(["BTC: fallback close (HTTPError)"], sonda=_sonda_ok)
|
|
assert d.verdetto == V_GATEWAY and d.atteso is False and d.gravita == "🛑"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Gli errori si DERIVANO dal report, non si reinventano (P1).
|
|
# ---------------------------------------------------------------------------
|
|
def test_errori_dal_report_raccoglie_le_misure_gia_fatte():
|
|
r = {"assets": [{"asset": "BTC", "mark_src": "fallback close (HTTPError)"},
|
|
{"asset": "ETH", "mark_src": "mainnet"}],
|
|
"skh_feed_errors": {"BTC": "OnMaintenance: deribit 11051"},
|
|
"pos_error": "posizione non leggibile: api 500"}
|
|
errori = errori_dal_report(r)
|
|
assert any("fallback close" in e for e in errori)
|
|
assert any("11051" in e for e in errori)
|
|
assert any("api 500" in e for e in errori)
|
|
# e la catena completa deve concludere MANUTENZIONE senza nemmeno sondare
|
|
assert diagnose(errori, sonda=_sonda_ok).verdetto == V_MANUTENZIONE
|
|
|
|
|
|
def test_report_vuoto_non_esplode():
|
|
assert errori_dal_report({}) == []
|