"""Isolamento della suite dai FILE OPERATIVI VIVI. ⚠️ PERCHE' ESISTE (2026-08-25, difetto trovato addosso). Lanciando `uv run pytest` la suite ha scritto **$5.000 dentro `data/live/equity_seen.json`**, il watermark dell'equity reale del conto. Al primo giro successivo del book (09:47 UTC) l'esecutore ha confrontato l'equity vera contro quel valore e ha mandato un allarme Telegram: 💰 USCITA DI FONDI: $5,000.00 -> $667.68 (-86.6%) **Falso, e generato da un test.** Il colpevole e' `test_book_live.py::test_la_formula_del_report_ha_potenza_anche_a_libro_flat`: prende solo `monkeypatch` (niente `tmp_path`), sostituisce `shadow_report` con uno che dichiara `real_equity=5000.0` e poi chiama `book.book_report()` — che come effetto collaterale scrive il watermark. L'helper `_write_cfg` esisteva gia' proprio per questo (difetto gemello del 2026-07-26), ma quel test, aggiunto il 21/08, non lo usa. **NON e' un fastidio cosmetico.** Il watermark alimenta il cap di fallback: cap_fallback = min(cap_fisso_di_config, watermark * frac) Con `watermark = $5.000` il cap diventa `min($3.000, $2.500) = $2.500` per asset invece di `$334`: su un conto da $668 sono fino a **$5.000 di nozionale lordo, ~7,5x di leva**. Ed e' raggiungibile: il ramo `eq_fallback` di `book_execute` **allerta e NON blocca** per scelta dichiarata. Cioe' lanciare la suite di test poteva **armare esattamente il pericolo che il watermark esiste per impedire** (vedi `src/live/book.py`, nota del 2026-07-26 sul cap fisso e la leva 3,35x). **LA RIPARAZIONE E' STRUTTURALE, NON PER-TEST.** Chiedere a ogni autore di ricordarsi il monkeypatch e' la stessa scommessa che ha gia' perso due volte (26/07 e 21/08). Qui il reindirizzamento e' **autouse**: vale per ogni test, anche per quelli che verranno scritti domani da chi non ha letto questo file. Un test che vuole davvero pilotare il watermark continua a funzionare — il suo monkeypatch esplicito vince su questo, che gira prima. """ import sys from pathlib import Path import pytest PROJECT_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(PROJECT_ROOT)) # ============================================================================================= # IL CANALE DI ALLARME VERO # ============================================================================================= # ⚠️ 2026-08-28, difetto trovato addosso di nuovo. Lanciando `uv run pytest` la suite ha mandato # messaggi Telegram VERI sul canale di allarme dell'operatore: cinque per giro, col testo delle # fixture — «Analisi di ieri.», «rete KO», «Testo dell'agente.» — sotto l'intestazione di una voce # di giornale del 2026-08-21. Colpevoli: i test di `analista.CLI.scrivi()`, che non passavano # `telegram=False` e quindi percorrevano `invia()` fino a `notifier.send()`. `_cfg()` legge il # token da `.env.mainnet`/`.env`, quindi il fatto che il token non fosse nell'ambiente non # proteggeva niente. # # NON E' COSMETICO. E' P9 al contrario: allarmi finti spesi su un canale che deve restare # credibile il giorno che l'allarme e' vero. Chi riceve cinque messaggi identici a ogni giro di # test impara a non aprirli — ed e' l'unico canale da cui passano il disaster-SL, l'uscita di # fondi e il venue giu'. # # LA RIPARAZIONE E' STRUTTURALE (stessa lezione del watermark): si blocca la RETE, non si chiede # a ogni autore di ricordarsi un parametro. Un test che vuole davvero esercitare `send()` continua # a funzionare: patcha `urlopen` da se' nel corpo, e il suo monkeypatch vince su questo. _INVII_TENTATI: list[str] = [] @pytest.fixture(autouse=True) def _blocca_il_canale_di_allarme_vero(monkeypatch): """Nessun test puo' raggiungere Telegram. Il tentativo si REGISTRA (P3: l'errore si scrive nel punto in cui lo si ingoia — `send()` cattura tutto, quindi senza questo elenco un invio tentato sparirebbe senza lasciare traccia).""" from src.live import notifier def _vietato(url, *a, **k): # ⚠️ NON si registra l'URL: contiene il BOT TOKEN in chiaro, e questa lista finisce # dentro il messaggio di un assert — cioe' nell'output della suite, nei log di CI e # nello scrollback di chiunque lanci i test. Il difetto e' stato fatto e corretto il # 2026-08-28: la prima versione stampava `https://api.telegram.org/bot/sendM…`. # Qui serve sapere CHE si e' tentato, non VERSO DOVE. _INVII_TENTATI.append("telegram sendMessage") raise RuntimeError("invio Telegram VERO bloccato dalla suite (tests/conftest.py)") monkeypatch.setattr(notifier.urllib.request, "urlopen", _vietato) @pytest.fixture(autouse=True, scope="session") def _guardia_nessun_invio_vero(): """Bloccare non basta: se un test PROVA a mandare, va trovato e reso esplicito. Un blocco silenzioso lascerebbe il tentativo in giro pronto a tornare vero il giorno che qualcuno cambia il boundary.""" yield assert not _INVII_TENTATI, ( f"{len(_INVII_TENTATI)} test hanno PROVATO a mandare un messaggio Telegram vero " "(bloccati, ma il tentativo non deve esistere): passare `telegram=False` o iniettare " "un sender finto. Vedi la nota in testa a questo file.") @pytest.fixture(autouse=True) def _isola_file_operativi_vivi(tmp_path, monkeypatch): """Ogni test scrive il watermark in una cartella temporanea, mai in `data/live/`. Il valore iniziale NON viene precaricato: un test che si aspetta un watermark deve dichiararlo, e la prima lettura in assoluto non produce allarmi per costruzione (`write_equity_watermark` ritorna None quando non c'e' un "prima"). """ import src.live.book as book monkeypatch.setattr(book, "EQUITY_WATERMARK", tmp_path / "equity_seen.json") # --- trades.db (2026-08-26, estensione §5.12) -------------------------------------------- # ⚠️ `connect(path=DB_PATH)` cattura il default ALLA DEFINIZIONE: patchare la costante # `DB_PATH` non cambierebbe niente. Si avvolge la funzione: un test che passa il path # esplicito (tutti quelli esistenti usano `T.connect(tmp_path / ...)`) non cambia # comportamento; un test che chiamasse `connect()` nudo finisce in tmp, non sul libro # di bordo vivo. import src.live.tradesdb as tradesdb _connect_vero = tradesdb.connect def _connect_isolato(path=None): return _connect_vero(tmp_path / "trades.db" if path is None else path) monkeypatch.setattr(tradesdb, "connect", _connect_isolato) # --- docs/journal/ ----------------------------------------------------------------------- # `journal.scrivi_file` scrive pagine markdown TRACCIATE DA GIT: un test che lo chiama # non deve poter sporcare il working tree (P16: il cron gira dal working tree). import src.live.journal as journal monkeypatch.setattr(journal, "JOURNAL_DIR", tmp_path / "journal") yield @pytest.fixture(autouse=True, scope="session") def _guardia_log_esecuzioni_vivo(): """`book_executions.jsonl` e' scritto da `book_execute.py`, che i test caricano ad-hoc con importlib: nessun monkeypatch di conftest puo' raggiungerlo PRIMA. Qui si fa l'altra meta' del lavoro: impronta a inizio suite, verifica a fine suite. Se questo assert scatta, un test ha scritto nel registro VIVO delle esecuzioni — trovarlo e dargli un path suo. Falso positivo possibile ma raro: un FILL REALE del cron :47 mentre la suite gira (30 fill in 43 giorni). In quel caso l'orario dell'ultima riga del file coincide col giro del cron, non con la suite: verificarlo prima di dare la colpa a un test.""" f = PROJECT_ROOT / "data" / "live" / "book_executions.jsonl" prima = (f.stat().st_mtime_ns, f.stat().st_size) if f.exists() else None yield dopo = (f.stat().st_mtime_ns, f.stat().st_size) if f.exists() else None assert prima == dopo, ( "un test ha scritto in data/live/book_executions.jsonl (registro VIVO): " f"{prima} -> {dopo}. Se l'ultima riga del file e' delle :47 puo' essere un fill " "reale del cron; altrimenti trovare il test e isolarlo.")