From baf6ddbe9e38c21965d4709ee26879acdf0fa121 Mon Sep 17 00:00:00 2001 From: Jeroen Grusewski Date: Tue, 21 Jul 2026 00:53:51 +0000 Subject: [PATCH] test(vrp): port archived-hide coverage to synthetic entry + honest test narratives (review fixes) --- src/fxhnt/application/exec_reconcile.py | 2 +- tests/integration/test_dashboard_service.py | 29 +++++++++++++ tests/integration/test_ibkr_sim_web.py | 48 +++++++++++---------- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/fxhnt/application/exec_reconcile.py b/src/fxhnt/application/exec_reconcile.py index 22332a7..b2a6ef7 100644 --- a/src/fxhnt/application/exec_reconcile.py +++ b/src/fxhnt/application/exec_reconcile.py @@ -9,7 +9,7 @@ module does NO new compute, only comparison, plus a read of the strategy-tagged cost-to-trade inputs (median slippage, total fees). `has_exec` is the gate: when the `_exec` track has no persisted forward_summary row yet (a real account -that hasn't run, or an exec twin that's suspended — e.g. `vrp_exec` today), there is nothing to reconcile +that hasn't run, or an exec twin that's suspended pending a fund decision), there is nothing to reconcile against — every other field defaults to zero rather than computing on unavailable data.""" from __future__ import annotations diff --git a/tests/integration/test_dashboard_service.py b/tests/integration/test_dashboard_service.py index d3224e2..60e69c5 100644 --- a/tests/integration/test_dashboard_service.py +++ b/tests/integration/test_dashboard_service.py @@ -9,6 +9,7 @@ from fxhnt.application.dashboard_service import DashboardService from fxhnt.application.forward_models import BacktestSummary from fxhnt.application.forward_models import ForwardNavRow as Row from fxhnt.application.forward_models import ForwardSummary +from fxhnt.registry import STRATEGY_REGISTRY def _seeded() -> ForwardNavRepo: @@ -92,3 +93,31 @@ def test_fleet_no_backtest_renders_blank() -> None: assert unlock.bt_as_of == "" +def test_archived_sleeves_hidden_everywhere(monkeypatch) -> None: + # A SYNTHETIC archived registry entry (`STRATEGY_REGISTRY` `archived: True` — the pattern + # `test_paper_books_web.py` uses for its synthetic `archivedstrat`/`newstrat`, in place of the deleted + # vrp-based test) must be excluded from every cockpit surface `ForwardNavRepo.registry()` feeds: + # `DashboardService.fleet()` and `.detail()`. Monkeypatch BEFORE `migrate()` so `_seed_registry()` (which + # seeds the DB registry table FROM `STRATEGY_REGISTRY` and prunes anything not present there) picks up + # the synthetic entry. + monkeypatch.setitem(STRATEGY_REGISTRY, "archivedstrat", + {"archived": True, "display_name": "Options income (put spreads)", + "sleeve": "equity-vol", "gate_spec": {}}) + repo = _seeded() # seeds registry AFTER the monkeypatch above, so archivedstrat lands in the DB row set + svc = DashboardService(repo) + + # registry() itself: the archived row never comes back, a non-archived row (unlock) does. + reg_ids = {r.strategy_id for r in repo.registry()} + assert "archivedstrat" not in reg_ids + assert "unlock" in reg_ids + + # fleet(): archived sleeve excluded; a live sleeve still shows. + fleet = svc.fleet() + assert all(f.strategy_id != "archivedstrat" for f in fleet) + assert any(f.strategy_id == "unlock" for f in fleet) + + # detail(): direct hit on the archived id returns None (404 at the route layer); a live id still resolves. + assert svc.detail("archivedstrat") is None + assert svc.detail("unlock") is not None + + diff --git a/tests/integration/test_ibkr_sim_web.py b/tests/integration/test_ibkr_sim_web.py index c8115a4..d0d0ec6 100644 --- a/tests/integration/test_ibkr_sim_web.py +++ b/tests/integration/test_ibkr_sim_web.py @@ -4,20 +4,24 @@ from the PRECOMPUTED `sim_curve_ret` table (never recomputed on the request path measured-cost path's shape (curve + metrics + Live forward), with plain-language pill labels (never the raw registry id "multistrat"). -NOTE: vrp was ARCHIVED/FALSIFIED (shelved 2026-07-15) — its `STRATEGY_REGISTRY` entry carries -`archived: True`, so it is EXCLUDED from the Backtest book pills (`_SIM_BOOKS`) and a `?book=vrp` request -falls back to the default book. The recompute-replay machinery itself still supports vrp (code + OPRA data -kept); this test file now asserts vrp is HIDDEN from the sim UI (see the archived-book tests below). +NOTE: vrp has been FULLY REMOVED from the codebase (shelved 2026-07-15, code+data deleted in the +`chore/remove-vrp-code` cleanup) — there is no longer a `STRATEGY_REGISTRY` entry, `_SIM_BOOKS` pill, or +recompute-replay support for it at all. This test file uses a synthetic non-existent book id +(`no-such-book`) as an honest stand-in to probe the SAME fallback behavior an archived/unknown book must +still get: never selectable, never rendered, always a graceful fallback to the default book (see the +archived-book tests below). Asserts: - * /paper/sim shows the multistrat pill labelled in plain language, not the raw id, and NO vrp pill; + * /paper/sim shows the multistrat pill labelled in plain language, not the raw id, and NO stray pill for an + unknown book; * /paper/sim/run?book=multistrat renders a real curve + metrics from the precomputed sim_curve_ret series; * capital scales the curve linearly (same contract as the Bybit measured path); * the request path never recomputes the replay (a tripwire on ForwardStrategy.advance would fire if it did — proven implicitly: the seeded repo carries no build_strategy at all, so a recompute is structurally impossible; the curve still renders from sim_curve_ret alone); * the Live forward section renders for multistrat (its own forward track), not just bybit_4edge; - * an archived book (vrp) is not selectable — the request falls back to the default book, never rendering vrp; + * an unknown/archived book id is not selectable — the request falls back to the default book, never + rendering that id; * absent precompute -> the graceful 'precomputing' note (never a 500, never the Bybit-specific caption). NO network — in-memory SQLite repos, seeded directly. """ @@ -47,7 +51,7 @@ def _seed_ibkr_curve(repo: PaperRepo, book: str, *, start: dt.date = dt.date(202 repo.upsert_sim_curve_ret(book, d, ret, at=at) -def _seeded_paper_repo(*, books: tuple[str, ...] = ("multistrat", "vrp")) -> PaperRepo: +def _seeded_paper_repo(*, books: tuple[str, ...] = ("multistrat", "no-such-book")) -> PaperRepo: repo = PaperRepo("sqlite://") repo.migrate() for b in books: @@ -89,16 +93,14 @@ def _sim_data(html: str) -> dict: return json.loads(m.group(1)) -def test_sim_page_shows_multistrat_pill_and_hides_archived_vrp() -> None: +def test_sim_page_shows_multistrat_pill_and_hides_unknown_book() -> None: c = _client(_seeded_paper_repo()) html = c.get("/paper/sim").text assert "ETF Portfolio" in html - # vrp is ARCHIVED — neither its pill label nor its raw id may appear anywhere on the Backtest page. - assert "Options income (put spreads)" not in html - assert "Equity VRP" not in html - assert 'data-book="vrp"' not in html and "book=vrp" not in html + # a book id absent from `_SIM_BOOKS` (unknown or archived) must never get a pill on the Backtest page. + assert 'data-book="no-such-book"' not in html and "book=no-such-book" not in html # the raw registry id must never leak as pill TEXT (it is fine as a query-string value). - assert ">multistrat<" not in html and ">vrp<" not in html + assert ">multistrat<" not in html and ">no-such-book<" not in html def test_multistrat_book_is_selectable_and_lazy_loads() -> None: @@ -121,14 +123,14 @@ def test_multistrat_run_renders_curve_and_metrics_from_precomputed_sim_curve_ret assert 80000 < data["equity"][0] < 120000 # curve starts near the configured capital -def test_archived_vrp_run_falls_back_and_never_renders_vrp() -> None: - """An archived book (vrp) is not in `_SIM_BOOKS`, so `/paper/sim/run?book=vrp` falls back to the default - book (bybit_4edge) — it renders that book's view (or the graceful pending note), never vrp's curve/label.""" +def test_unknown_book_run_falls_back_and_never_renders_it() -> None: + """A book id absent from `_SIM_BOOKS` (unknown or archived) makes `/paper/sim/run?book=no-such-book` + fall back to the default book (bybit_4edge) — it renders that book's view (or the graceful pending + note), never the unknown id's curve/label.""" c = _client(_seeded_paper_repo()) - r = c.get("/paper/sim/run", params={"book": "vrp", "capital": 100000}) + r = c.get("/paper/sim/run", params={"book": "no-such-book", "capital": 100000}) assert r.status_code == 200 # graceful fallback, never a 500 - assert "Options income (put spreads)" not in r.text and "Equity VRP" not in r.text - assert 'data-book="vrp"' not in r.text # the effective book is the default, not vrp + assert 'data-book="no-such-book"' not in r.text # the effective book is the default, not the unknown id def test_ibkr_capital_scales_the_curve_linearly() -> None: @@ -180,12 +182,12 @@ def test_ibkr_missing_precompute_shows_pending_note_not_bybit_caption() -> None: assert 'data-cost-mode="flat"' not in r.text -def test_sim_book_switch_covers_visible_books_and_excludes_archived_vrp() -> None: +def test_sim_book_switch_covers_visible_books_and_excludes_unknown_book() -> None: c = _client(_seeded_paper_repo()) # the three VISIBLE books each render their own page... for book in ("bybit_4edge", "bybit_4edge_levered", "multistrat"): r = c.get("/paper/sim", params={"book": book}) assert r.status_code == 200 and f'data-book="{book}"' in r.text - # ...but the ARCHIVED vrp is not selectable: it falls back to the default book (200, never vrp). - r = c.get("/paper/sim", params={"book": "vrp"}) - assert r.status_code == 200 and 'data-book="vrp"' not in r.text + # ...but an unknown/archived book id is not selectable: it falls back to the default book (200, never it). + r = c.get("/paper/sim", params={"book": "no-such-book"}) + assert r.status_code == 200 and 'data-book="no-such-book"' not in r.text