"""Phase 3 acceptance: the UI renders, and every displayed number is traceable. These run against the real cached store when it is present, and skip cleanly when it is not (a fresh clone with no `.cache/store` yet). """ from __future__ import annotations import re import sys from pathlib import Path import pandas as pd import plotly.graph_objects as go import pytest sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) import app as bitapp from src import charts, comparisons, config, runtime, strategies from src.ui import components as C from src.ui.format import money, num, pct from src.runtime import RunRequest def _store_ready() -> bool: try: m = runtime.get_store().load_manifest() return len(m.prices) > 0 except Exception: return False pytestmark = pytest.mark.skipif(not _store_ready(), reason="no cached signal store available") @pytest.fixture(scope="module") def rec(): return runtime.execute(RunRequest( strategy="SMA Crossover", asset="BTC-USD", timeframe="1d", date_range="3Y", )) @pytest.fixture(scope="module") def three_runs(): reqs = [ RunRequest(strategy="SMA Crossover", asset="BTC-USD", timeframe="1d", date_range="3Y"), RunRequest(strategy="RSI Mean Reversion", asset="ETH-USD", timeframe="1d", date_range="3Y"), RunRequest(strategy="Buy & Hold (benchmark)", asset="SPY", timeframe="1d", date_range="3Y"), ] return [runtime.execute(r) for r in reqs] # -------------------------------------------------------------------------- # App construction # -------------------------------------------------------------------------- def test_app_object_exists(): assert bitapp.demo is not None def test_theme_css_carries_the_design_tokens(): from src.ui import theme css = theme.full_css() for token in ("--bg-canvas", "--accent-amber", "--fin-up", "--font-styrene"): assert token in css, f"{token} missing from theme CSS" assert "#161512" in css # stone-950 canvas assert "#af9209" in css # accent amber def test_disclaimer_is_present_in_the_footer(): assert "not indicative of future results" in C.footer() assert "Not financial advice" in C.footer() or \ "not a licensed investment adviser" in C.footer() def test_empty_state_offers_the_worked_example(): assert "No run loaded" in C.empty_state() assert "worked example" in C.empty_state() def test_glossary_covers_every_design_term(): terms = {t for t, _ in bitapp.GLOSSARY} assert {"SHARPE", "SORTINO", "MAX DRAWDOWN", "PROFIT FACTOR", "R-MULTIPLE", "MAE / MFE", "WALK-FORWARD", "OOS"} <= terms # -------------------------------------------------------------------------- # The stat band must match engine output exactly # -------------------------------------------------------------------------- def test_stat_band_values_match_engine_metrics(rec): html = C.stat_band(rec) m = rec.result.metrics_all assert pct(m.total_return) in html assert pct(m.cagr) in html assert num(m.sharpe) in html assert num(m.sortino) in html assert pct(m.max_drawdown) in html assert num(m.profit_factor) in html assert f">{m.trade_count}<" in html def test_stat_band_shows_is_and_oos_for_every_stat(rec): html = C.stat_band(rec) assert html.count("IS ") >= 9 assert html.count("· OOS") >= 9 assert num(rec.result.metrics_oos.sharpe) in html assert num(rec.result.metrics_is.sharpe) in html def test_stat_band_reports_costs_actually_paid(rec): html = C.stat_band(rec) assert money(rec.result.costs_paid) in html assert rec.result.costs_paid > 0, "costs default to ON, so this must be positive" def test_empty_segment_renders_an_em_dash_not_a_zero(): """A segment with no bars must not read as 0.00.""" short = runtime.execute(RunRequest( strategy="SMA Crossover", asset="BTC-USD", timeframe="1h", date_range="1Y")) if short.result.metrics_oos.bars: pytest.skip("this range did produce OOS windows") html = C.stat_band(short) assert "· OOS —" in html assert "no out-of-sample windows" in html def test_trade_table_rows_match_the_engine_trade_list(rec): df = bitapp.trades_frame(rec) assert len(df) == len(rec.result.trades) if len(df): assert df["Net"].iloc[0] == pytest.approx( round(float(rec.result.trades["net_pnl"].iloc[0]), 2)) assert set(df["Segment"]) <= {"IS", "OOS", "holdout"} assert (df["Costs"] >= 0).all() def test_report_quotes_the_same_numbers_as_the_stat_band(rec): md = bitapp.report_markdown(rec) assert pct(rec.result.metrics_all.total_return) in md assert money(rec.result.costs_paid) in md assert rec.run_id in md def test_costs_off_is_called_out_as_not_real(): off = runtime.execute(RunRequest( strategy="SMA Crossover", asset="BTC-USD", timeframe="1d", date_range="3Y", costs_on=False)) _, _, _, _, _, _, _, _, note = bitapp.build_overview(off, log_scale=False, cvd=False) assert "COSTS ARE OFF" in note assert off.result.costs_paid == 0.0 # -------------------------------------------------------------------------- # Charts # -------------------------------------------------------------------------- def test_overview_builds_every_figure(rec): figs = bitapp.build_overview(rec, log_scale=False, cvd=False) assert len(figs) == 9 for f in figs[:8]: assert isinstance(f, go.Figure) assert isinstance(figs[8], str) def test_log_scale_and_colorblind_variants_render(rec): for log_s in (False, True): for cb in (False, True): figs = bitapp.build_overview(rec, log_scale=log_s, cvd=cb) assert isinstance(figs[0], go.Figure) def test_equity_chart_marks_the_holdout_band(rec): fig = charts.equity_curve(rec.result.equity, rec.result.benchmark_equity, plan=rec.result.plan) if rec.result.plan.holdout_start is not None: texts = [str(a.text) for a in fig.layout.annotations] assert any("HOLDOUT" in t for t in texts) def test_charts_survive_empty_inputs(): empty = pd.Series(dtype="float64") assert isinstance(charts.equity_curve(empty), go.Figure) assert isinstance(charts.underwater_chart(empty), go.Figure) assert isinstance(charts.pnl_histogram(pd.DataFrame()), go.Figure) assert isinstance(charts.mae_mfe_scatter(pd.DataFrame()), go.Figure) assert isinstance(charts.walk_forward_bars([]), go.Figure) assert isinstance(charts.monte_carlo_cone(None), go.Figure) def test_colorblind_palette_differs_from_default(): assert charts.up_color(cvd=True) != charts.up_color(cvd=False) assert charts.down_color(cvd=True) != charts.down_color(cvd=False) # -------------------------------------------------------------------------- # Comparison tab with three runs # -------------------------------------------------------------------------- def test_comparison_renders_three_runs(three_runs): curves = {r.label[:28]: r.result.equity for r in three_runs} rets = {r.label[:28]: r.result.equity.pct_change().dropna() for r in three_runs} assert len(curves) == 3 assert isinstance(charts.overlaid_returns(curves), go.Figure) assert isinstance(charts.small_multiples(curves), go.Figure) corr = charts.correlation_matrix(rets) assert isinstance(corr, go.Figure) assert len(corr.data[0].z) == 3 def test_return_overlay_caps_the_series_it_will_draw(): """The old six-run session picker was replaced by the catalog view; the readability cap now lives in the chart itself.""" import numpy as np idx = pd.date_range("2024-01-01", periods=30, freq="D", tz="UTC") curves = {f"s{i}": pd.Series(np.linspace(0, 1, 30), index=idx) for i in range(50)} assert len(charts.multi_return_overlay(curves, max_series=6).data) == 6 def test_precomputed_heatmap_loads_from_the_store(): heat = comparisons.load_table(runtime.get_store(), comparisons.HEATMAP) if heat.empty: pytest.skip("comparison tables not generated yet") assert {"asset", "strategy", "timeframe", "oos_sharpe"} <= set(heat.columns) assert isinstance(charts.strategy_timeframe_heatmap(heat), go.Figure) def test_regime_breakdown_covers_the_named_regimes(rec): df = runtime.regime_breakdown(rec) if df.empty: pytest.skip("no regime variation in this window") assert set(df["regime"]) <= {"BULL", "BEAR", "CHOP"} # -------------------------------------------------------------------------- # Coverage map & share links # -------------------------------------------------------------------------- def test_coverage_map_renders_from_the_live_manifest(): df = runtime.coverage_frame() assert not df.empty assert {"Model", "Asset", "TF", "Coverage", "Rows", "Real?"} <= set(df.columns) def test_placeholder_slices_are_labelled_in_the_coverage_map(): df = runtime.coverage_frame() assert set(df["Real?"]) <= {"real", "PLACEHOLDER"} def test_share_link_round_trips(): req = RunRequest(strategy="RSI Mean Reversion", asset="ETH-USD", timeframe="1h", date_range="1Y", params={"rsi_period": 21}) again = RunRequest.decode(req.encode()) assert again.strategy == req.strategy assert again.asset == req.asset assert again.params["rsi_period"] == 21 @pytest.mark.parametrize("payload", [ '{"strategy":"__import__(\'os\').system","asset":"BTC-USD"}', '{"strategy":"SMA Crossover","asset":"../../etc/passwd"}', '{"strategy":"SMA Crossover","asset":"BTC-USD","timeframe":"99y"}', '{"strategy":"SMA Crossover","asset":"BTC-USD","validation_mode":"eval"}', ]) def test_hostile_share_links_are_rejected(payload): import base64 token = base64.urlsafe_b64encode(payload.encode()).decode().rstrip("=") with pytest.raises(ValueError): RunRequest.decode(token) def test_unavailable_presets_are_listed_but_refuse_to_run(): assert "Custom (code)" in strategies.PRESETS assert not strategies.PRESETS["Custom (code)"].available with pytest.raises(runtime.RunError, match="never executes untrusted code"): runtime.execute(RunRequest(strategy="Custom (code)", asset="BTC-USD")) # -------------------------------------------------------------------------- # Performance budget # -------------------------------------------------------------------------- def test_default_three_year_daily_run_is_under_two_seconds(): runtime.cache_clear() req = RunRequest(strategy="SMA Crossover", asset="BTC-USD", timeframe="1d", date_range="3Y") runtime.execute(req) # warm the slice cache import time t0 = time.perf_counter() runtime.execute(req) assert time.perf_counter() - t0 < 2.0 # -------------------------------------------------------------------------- # Design-system fidelity # # The original bug this guards: base.css was vendored into assets/ but never # loaded, so the app silently inherited Gradio's rounded, roomy defaults and # only approximated the design. # -------------------------------------------------------------------------- def test_every_global_stylesheet_the_design_system_declares_is_loaded(): """The DS manifest lists six globalCssPaths; all of them must reach the page.""" from src.ui import theme css = theme.full_css() # colors / typography / spacing / base, each identified by a rule only it has markers = { "colors.css": "--accent-amber:#af9209", "typography.css": "--text-2xs:8px", "spacing.css": "--space-4:16px", "base.css": "border-radius:0 !important", } for name, marker in markers.items(): assert marker in css, f"{name} is not loaded into the page" def test_base_css_square_corner_reset_is_present(): from src.ui import theme assert "border-radius:0 !important" in theme.full_css() # The app's own shell must not reintroduce a rounded default on top of the # reset. base.css legitimately defines opt-in `.bit-rounded*` utilities, so # only our shell layer is checked here. assert "border-radius" not in theme.SHELL_CSS assert "border-radius" not in theme.GRADIO_RESET def test_base_css_scrollbar_and_selection_rules_survive(): from src.ui import theme css = theme.full_css() assert "::-webkit-scrollbar" in css assert "::selection" in css def test_data_surfaces_stay_selectable_despite_base_css(): """base.css sets user-select:none globally; tables must opt back in.""" from src.ui import theme css = theme.full_css() assert "user-select: text" in css assert ".bit-table" in css.split("user-select: text")[0][-400:] or \ "table, table *" in css def test_gradio_layout_spacing_is_configured_not_left_default(): """Gradio defaults to layout_gap *spacing_xxl and block_padding *spacing_xl.""" from src.ui import theme t = theme.bit_theme() assert t.layout_gap == "8px" assert t.block_padding == "0px" assert t.form_gap_width == "0px" def test_gradio_radius_is_squared_off(): from src.ui import theme t = theme.bit_theme() for attr in ("block_radius", "input_radius", "button_large_radius", "button_small_radius", "container_radius"): assert getattr(t, attr) == "0px", f"{attr} is not square" def test_type_scale_matches_the_designs_dominant_sizes(): """The design's workhorse is --text-xs (10px) with --text-2xs (8px) micro.""" from src.ui import theme t = theme.bit_theme() assert t.body_text_size == "10px" assert t.block_label_text_size == "8px" def test_no_hardcoded_pixel_font_sizes_outside_the_theme(): """Component markup must size through tokens, never literal px.""" import re from pathlib import Path root = Path(__file__).resolve().parent.parent for rel in ("src/ui/components.py", "src/ui/compare_tab.py", "app.py"): text = (root / rel).read_text() assert not re.search(r"font-size:\s*\d", text), \ f"{rel} hardcodes a pixel font-size instead of using a token" def test_every_bit_class_used_in_markup_has_a_css_rule(): """A class with no rule renders unstyled and silently breaks the design.""" import re from pathlib import Path from src.ui import theme root = Path(__file__).resolve().parent.parent used = set() for rel in ("src/ui/components.py", "src/ui/compare_tab.py", "app.py"): text = (root / rel).read_text() # Only real class attributes -- a bare `bit-…` token can appear in a # comment or an asset filename and is not a class the page uses. for attr in (re.findall(r'class="([^"]*)"', text) + re.findall(r"elem_classes=[\"']([^\"']+)", text)): for cls in attr.split(): if not cls.startswith("bit-"): continue # Class names are built in f-strings, e.g. `bit-chip{cls}` or # `bit-podium-{rank}`. Keep the static prefix; a name that is # only a prefix (ends in `-`) has variant rules instead. static = cls.split("{", 1)[0] if static and not static.endswith("-"): used.add(static) defined = set(re.findall(r"\.(bit-[a-z0-9-]+)", theme.full_css())) missing = sorted(c for c in used if c not in defined and not c.endswith("-")) assert not missing, f"classes used in markup but never styled: {missing}" def test_fonts_are_served_for_every_weight_the_design_uses(): from src.ui import theme css = theme.full_css() assert css.count("@font-face") >= 5 assert "Styrene A" in css and "Mac Minecraft" in css def test_app_constructs_without_any_hugging_face_credentials(monkeypatch): """Reading and backtesting are open to anyone, so the app must build with no token at all. Gradio's off-Space OAuth mock calls whoami and raises without one, which previously made the whole app unconstructable.""" import importlib for var in ("HF_TOKEN", "HF_WRITE_TOKEN", "HUGGING_FACE_HUB_TOKEN", "SPACE_ID"): monkeypatch.delenv(var, raising=False) import app as fresh importlib.reload(fresh) assert fresh.demo is not None # -------------------------------------------------------------------------- # Design-markup rendering (shell) and the click bridge # -------------------------------------------------------------------------- def test_left_panel_is_design_markup_not_gradio_components(): """Every control in the left panel must be a real button or input.""" from src.ui import shell st = bitapp.default_state() html = bitapp.render_left(st) assert 'width:286px' in html, "zone width is not the design's 286px" assert html.count("= 12, "controls are not real buttons" assert "data-bit=" in html, "buttons are not wired to the bridge" # Gradio's own control DOM must not appear here. assert "]*background:var\(--accent-amber\)', html) assert actives == ["tf:1h"] def test_html_entities_are_escaped_exactly_once(): html = bitapp.render_left(bitapp.default_state()) assert "&amp;" not in html, "double-escaped entity" assert "Universe & Data" in html @pytest.mark.parametrize("action,key,expected", [ ("tf:15m", "timeframe", "15m"), ("asset:ETH-USD", "asset", "ETH-USD"), ("range:1Y", "range", "1Y"), ("validation:holdout", "validation_mode", "holdout"), ("sizing:vol_target", "sizing_mode", "vol_target"), ("slippage:volume_scaled", "slippage_model", "volume_scaled"), ]) def test_actions_fold_into_state(action, key, expected): st, ran = bitapp.apply_action(bitapp.default_state(), f"{action}|nonce") assert st[key] == expected assert ran is False def test_run_and_example_actions_request_a_run(): _, ran = bitapp.apply_action(bitapp.default_state(), "run:|n") assert ran is True st, ran = bitapp.apply_action(bitapp.default_state(), "example:|n") assert ran is True and st["strategy"] == "Chronos Forecast Follower" def test_accordion_action_toggles(): st = bitapp.default_state() before = st["acc"]["costs"] st, _ = bitapp.apply_action(st, "acc:costs|n") assert st["acc"]["costs"] is not before def test_numeric_param_commits_through_the_bridge(): st, _ = bitapp.apply_action(bitapp.default_state(), "param:commission_bps=25|n") assert st["commission_bps"] == 25.0 def test_strategy_param_commits_and_survives_preset_defaults(): st = bitapp.default_state() st, _ = bitapp.apply_action(st, "param:fast_ma=33|n") assert st["params"]["fast_ma"] == 33.0 @pytest.mark.parametrize("bad", [ "param:commission_bps=nonsense", "param:commission_bps=", "topn:abc", "tf:99y", "asset:../etc/passwd", "strategy:__import__", ]) def test_malformed_actions_leave_state_untouched(bad): before = bitapp.default_state() after, ran = bitapp.apply_action(bitapp.default_state(), f"{bad}|n") assert ran is False assert after["timeframe"] == before["timeframe"] assert after["asset"] == before["asset"] assert after["strategy"] == before["strategy"] assert after["commission_bps"] == before["commission_bps"] def test_unknown_action_keys_are_dropped(): from src.ui.bridge import parse_action assert parse_action("evil:rm -rf|n") is None assert parse_action("os.system:x|n") is None def test_bridge_survives_values_containing_spaces(): """Preset names contain spaces; the nonce separator must not collide.""" from src.ui.bridge import emit, parse_action raw = emit("strategy", "Buy & Hold (benchmark)") assert parse_action(raw + "|nonce").value == "Buy & Hold (benchmark)" def test_tables_render_as_markup_not_dataframes(): df = pd.DataFrame({"A": [1, 2], "B": ["x", "y"]}) html = bitapp.render_table(df) assert "