""" Tests for dolphin_tui helper utilities. Validates: Requirements 3.2 """ import sys import os # Allow importing dolphin_tui without its optional heavy deps (textual, hazelcast, httpx) sys.path.insert(0, os.path.dirname(__file__)) import types # Stub out optional third-party imports so the module loads in a test environment for _mod in ("textual", "textual.app", "textual.widgets", "textual.containers", "httpx", "hazelcast"): if _mod not in sys.modules: sys.modules[_mod] = types.ModuleType(_mod) # Provide minimal stubs for textual symbols used at module level import textual as _textual import textual.app as _textual_app import textual.widgets as _textual_widgets import textual.containers as _textual_containers _textual_app.App = object _textual_app.ComposeResult = object _textual_widgets.Static = object _textual_widgets.VerticalScroll = object _textual_containers.Horizontal = object from dolphin_tui import rm_bar, fmt_pnl, posture_color, status_color # noqa: E402 # --------------------------------------------------------------------------- # Unit tests # --------------------------------------------------------------------------- def test_rm_bar_none_returns_placeholder(): assert rm_bar(None) == "--" def test_rm_bar_zero_all_empty(): result = rm_bar(0.0, width=10) assert result == "[░░░░░░░░░░] 0.00" def test_rm_bar_one_all_filled(): result = rm_bar(1.0, width=10) assert result == "[██████████] 1.00" def test_rm_bar_midpoint(): result = rm_bar(0.5, width=10) assert result == "[█████░░░░░] 0.50" def test_rm_bar_default_width_length(): result = rm_bar(0.82) # default width=20 → length should be 20 + 7 # '[' (1) + width bar chars + '] ' (2) + '{rm:.2f}' (4 chars for [0.00,1.00]) = width + 7 assert len(result) == 27 def test_rm_bar_format_suffix(): result = rm_bar(0.82, width=20) assert result.endswith("] 0.82") # --------------------------------------------------------------------------- # Property-based test # Validates: Requirements 3.2 # Property: rm_bar(rm, width) output length always equals width + 4 # for any rm in [0.0, 1.0] and any valid width. # --------------------------------------------------------------------------- from hypothesis import given, settings import hypothesis.strategies as st @given( rm=st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), width=st.integers(min_value=1, max_value=200), ) @settings(max_examples=500) def test_rm_bar_length_property(rm: float, width: int) -> None: """**Validates: Requirements 3.2** Property: for any rm in [0.0, 1.0] and any positive integer width, len(rm_bar(rm, width)) == width + 7. The +7 accounts for: '[' (1) + bar chars (width) + '] ' (2) + '{rm:.2f}' (4 chars for [0.00,1.00]) = 1 + width + 2 + 4 = width + 7 """ result = rm_bar(rm, width=width) assert len(result) == width + 7, ( f"rm_bar({rm!r}, width={width!r}) returned {result!r} " f"with length {len(result)}, expected {width + 7}" ) # --------------------------------------------------------------------------- # fmt_pnl tests # Validates: Requirements 7.3 # --------------------------------------------------------------------------- def test_fmt_pnl_none(): color, text = fmt_pnl(None) assert text == "--" assert color == "white" def test_fmt_pnl_positive(): color, text = fmt_pnl(1240.50) assert color == "green" assert text == "+$1,240.50" def test_fmt_pnl_negative(): color, text = fmt_pnl(-320.75) assert color == "red" assert text == "-$320.75" def test_fmt_pnl_zero(): color, text = fmt_pnl(0.0) assert color == "white" assert text == "$0.00" def test_fmt_pnl_large_positive(): color, text = fmt_pnl(1_234_567.89) assert color == "green" assert text == "+$1,234,567.89" # --------------------------------------------------------------------------- # Property-based test for fmt_pnl # Validates: Requirements 7.3 # Property: fmt_pnl always returns a (str, str) tuple; color is one of the # three valid values; text starts with '+$', '-$', or '$'. # --------------------------------------------------------------------------- @given(v=st.floats(allow_nan=False, allow_infinity=False)) @settings(max_examples=500) def test_fmt_pnl_property(v: float) -> None: """**Validates: Requirements 7.3** Property: fmt_pnl(v) always returns a valid (color, text) tuple for any finite float, with color in {green, red, white} and text formatted as a dollar amount with sign prefix. """ color, text = fmt_pnl(v) assert color in ("green", "red", "white") assert "$" in text if v > 0: assert color == "green" assert text.startswith("+$") elif v < 0: assert color == "red" assert text.startswith("-$") else: assert color == "white" # --------------------------------------------------------------------------- # posture_color tests # Validates: Requirements 3.1 # --------------------------------------------------------------------------- def test_posture_color_apex(): assert posture_color("APEX") == "green" def test_posture_color_stalker(): assert posture_color("STALKER") == "yellow" def test_posture_color_turtle(): assert posture_color("TURTLE") == "dark_orange" def test_posture_color_hibernate(): assert posture_color("HIBERNATE") == "red" def test_posture_color_unknown(): assert posture_color("UNKNOWN_POSTURE") == "dim" def test_posture_color_none(): assert posture_color(None) == "dim" def test_posture_color_empty_string(): assert posture_color("") == "dim" # --------------------------------------------------------------------------- # Property-based test for posture_color # Validates: Requirements 3.1 # Property: posture_color always returns a non-empty string; known postures # return their mapped color; anything else returns "dim". # --------------------------------------------------------------------------- from dolphin_tui import POSTURE_COLORS # noqa: E402 @given(posture=st.text()) @settings(max_examples=500) def test_posture_color_property(posture: str) -> None: """**Validates: Requirements 3.1** Property: posture_color(posture) always returns a non-empty string. Known postures map to their defined color; all others return "dim". """ result = posture_color(posture) assert isinstance(result, str) assert len(result) > 0 if posture in POSTURE_COLORS: assert result == POSTURE_COLORS[posture] else: assert result == "dim" # --------------------------------------------------------------------------- # status_color tests # Validates: Requirements 3.3 # --------------------------------------------------------------------------- def test_status_color_green(): assert status_color("GREEN") == "green" def test_status_color_degraded(): assert status_color("DEGRADED") == "yellow" def test_status_color_critical(): assert status_color("CRITICAL") == "dark_orange" def test_status_color_dead(): assert status_color("DEAD") == "red" def test_status_color_unknown(): assert status_color("UNKNOWN_STATUS") == "dim" def test_status_color_none(): assert status_color(None) == "dim" def test_status_color_empty_string(): assert status_color("") == "dim" # --------------------------------------------------------------------------- # Property-based test for status_color # Validates: Requirements 3.3 # Property: status_color always returns a non-empty string; known statuses # return their mapped color; anything else returns "dim". # --------------------------------------------------------------------------- from dolphin_tui import STATUS_COLORS # noqa: E402 @given(status=st.text()) @settings(max_examples=500) def test_status_color_property(status: str) -> None: """**Validates: Requirements 3.3** Property: status_color(status) always returns a non-empty string. Known statuses map to their defined color; all others return "dim". """ result = status_color(status) assert isinstance(result, str) assert len(result) > 0 if status in STATUS_COLORS: assert result == STATUS_COLORS[status] else: assert result == "dim" # --------------------------------------------------------------------------- # SystemHealthPanel tests # Validates: Requirements 6.1, 6.2, 6.3 # --------------------------------------------------------------------------- from dolphin_tui import SystemHealthPanel, DataSnapshot # noqa: E402 def _make_panel() -> SystemHealthPanel: """Create a SystemHealthPanel with Static stubbed to object.""" return SystemHealthPanel.__new__(SystemHealthPanel) def _init_panel() -> SystemHealthPanel: panel = SystemHealthPanel.__new__(SystemHealthPanel) panel._snap = None return panel def test_system_health_panel_no_snap_shows_dashes(): """With no snapshot, all fields render as '--'.""" panel = _init_panel() markup = panel._render_markup() assert "rm_meta: --" in markup assert "M1: --" in markup assert "M5: --" in markup assert "● --" in markup def test_system_health_panel_rm_meta_formatted(): """rm_meta is formatted to 3 decimal places.""" panel = _init_panel() panel._snap = DataSnapshot(meta_rm=0.9234567) markup = panel._render_markup() assert "rm_meta: 0.923" in markup def test_system_health_panel_m_scores_formatted(): """M1-M5 scores are formatted to 1 decimal place.""" panel = _init_panel() panel._snap = DataSnapshot( m1_proc=1.0, m2_heartbeat=0.9, m3_data=0.8, m4_cp=0.75, m5_coh=0.5, ) markup = panel._render_markup() assert "M1: 1.0" in markup assert "M2: 0.9" in markup assert "M3: 0.8" in markup assert "M4: 0.8" in markup # 0.75 rounds to 0.8 with 1 decimal assert "M5: 0.5" in markup def test_system_health_panel_status_green(): """GREEN status renders with [green] markup.""" panel = _init_panel() panel._snap = DataSnapshot(meta_status="GREEN") markup = panel._render_markup() assert "[green]● GREEN[/green]" in markup def test_system_health_panel_status_degraded(): """DEGRADED status renders with [yellow] markup.""" panel = _init_panel() panel._snap = DataSnapshot(meta_status="DEGRADED") markup = panel._render_markup() assert "[yellow]● DEGRADED[/yellow]" in markup def test_system_health_panel_status_critical(): """CRITICAL status renders with [dark_orange] markup.""" panel = _init_panel() panel._snap = DataSnapshot(meta_status="CRITICAL") markup = panel._render_markup() assert "[dark_orange]● CRITICAL[/dark_orange]" in markup def test_system_health_panel_status_dead(): """DEAD status renders with [red] markup.""" panel = _init_panel() panel._snap = DataSnapshot(meta_status="DEAD") markup = panel._render_markup() assert "[red]● DEAD[/red]" in markup def test_system_health_panel_none_fields_show_dashes(): """None fields in a snapshot render as '--', not crash.""" panel = _init_panel() panel._snap = DataSnapshot( meta_rm=None, meta_status=None, m1_proc=None, m2_heartbeat=None, m3_data=None, m4_cp=None, m5_coh=None, ) markup = panel._render_markup() assert "rm_meta: --" in markup assert "● --" in markup def test_system_health_panel_title_present(): """Panel markup always includes the SYSTEM HEALTH title.""" panel = _init_panel() markup = panel._render_markup() assert "SYSTEM HEALTH" in markup def test_system_health_panel_update_data_stores_snap(): """update_data stores the snapshot (update() call is a no-op on stub).""" panel = _init_panel() # Patch update to be a no-op since Static is stubbed panel.update = lambda _: None snap = DataSnapshot(meta_rm=0.5, meta_status="GREEN") panel.update_data(snap) assert panel._snap is snap # --------------------------------------------------------------------------- # AlphaEnginePanel tests # Validates: Requirements 3.1, 3.2, 3.3, 3.4, 12.3, 15.3 # --------------------------------------------------------------------------- from dolphin_tui import AlphaEnginePanel # noqa: E402 def _init_alpha_panel() -> AlphaEnginePanel: panel = AlphaEnginePanel.__new__(AlphaEnginePanel) panel._snap = None return panel def test_alpha_panel_no_snap_shows_dashes(): """With no snapshot, all fields render as '--'.""" panel = _init_alpha_panel() markup = panel._render_markup() assert "Posture:" in markup assert "--" in markup assert "Rm:" in markup assert "ACB:" in markup assert "Cat1:" in markup def test_alpha_panel_title_present(): """Panel markup always includes the ALPHA ENGINE title.""" panel = _init_alpha_panel() markup = panel._render_markup() assert "ALPHA ENGINE" in markup def test_alpha_panel_posture_apex_green(): """APEX posture renders with [green] markup (Req 3.1).""" panel = _init_alpha_panel() panel._snap = DataSnapshot(posture="APEX") markup = panel._render_markup() assert "[green]APEX[/green]" in markup def test_alpha_panel_posture_stalker_yellow(): """STALKER posture renders with [yellow] markup (Req 3.1).""" panel = _init_alpha_panel() panel._snap = DataSnapshot(posture="STALKER") markup = panel._render_markup() assert "[yellow]STALKER[/yellow]" in markup def test_alpha_panel_posture_turtle_dark_orange(): """TURTLE posture renders with [dark_orange] markup (Req 3.1).""" panel = _init_alpha_panel() panel._snap = DataSnapshot(posture="TURTLE") markup = panel._render_markup() assert "[dark_orange]TURTLE[/dark_orange]" in markup def test_alpha_panel_posture_hibernate_red(): """HIBERNATE posture renders with [red] markup (Req 3.1).""" panel = _init_alpha_panel() panel._snap = DataSnapshot(posture="HIBERNATE") markup = panel._render_markup() assert "[red]HIBERNATE[/red]" in markup def test_alpha_panel_posture_none_shows_dim_dash(): """None posture renders as dim '--' (Req 12.3).""" panel = _init_alpha_panel() panel._snap = DataSnapshot(posture=None) markup = panel._render_markup() assert "[dim]--[/dim]" in markup def test_alpha_panel_rm_bar_rendered( ): """Rm value renders as ASCII bar (Req 3.2).""" panel = _init_alpha_panel() panel._snap = DataSnapshot(rm=0.5) markup = panel._render_markup() assert "█" in markup assert "░" in markup assert "0.50" in markup def test_alpha_panel_rm_none_shows_dash(): """None Rm renders as '--' (Req 12.3).""" panel = _init_alpha_panel() panel._snap = DataSnapshot(rm=None) markup = panel._render_markup() assert "Rm: --" in markup def test_alpha_panel_acb_boost_beta_formatted(): """ACB boost and beta render with 2 decimal places (Req 3.3).""" panel = _init_alpha_panel() panel._snap = DataSnapshot(acb_boost=1.55, acb_beta=0.80) markup = panel._render_markup() assert "1.55x" in markup assert "β=0.80" in markup def test_alpha_panel_acb_none_shows_dashes(): """None ACB fields render as '--' (Req 12.3).""" panel = _init_alpha_panel() panel._snap = DataSnapshot(acb_boost=None, acb_beta=None) markup = panel._render_markup() assert "ACB: --x β=--" in markup def test_alpha_panel_cat_values_rendered(): """Cat1-Cat5 values render with 2 decimal places (Req 3.4).""" panel = _init_alpha_panel() panel._snap = DataSnapshot( cat1=0.9, cat2=0.8, cat3=0.7, cat4=1.0, cat5=0.5 ) markup = panel._render_markup() assert "0.90" in markup assert "0.80" in markup assert "0.70" in markup assert "1.00" in markup assert "0.50" in markup def test_alpha_panel_cat_none_shows_dashes(): """None Cat fields render as '--' (Req 12.3).""" panel = _init_alpha_panel() panel._snap = DataSnapshot( cat1=None, cat2=None, cat3=None, cat4=None, cat5=None ) markup = panel._render_markup() assert "--" in markup def test_alpha_panel_all_none_no_crash(): """All-None snapshot never crashes (Req 12.3).""" panel = _init_alpha_panel() panel._snap = DataSnapshot( posture=None, rm=None, acb_boost=None, acb_beta=None, cat1=None, cat2=None, cat3=None, cat4=None, cat5=None, ) markup = panel._render_markup() assert "ALPHA ENGINE" in markup def test_alpha_panel_update_data_stores_snap(): """update_data stores the snapshot.""" panel = _init_alpha_panel() panel.update = lambda _: None snap = DataSnapshot(posture="APEX", rm=0.82) panel.update_data(snap) assert panel._snap is snap def test_alpha_panel_cat_right_aligned(): """Cat values are right-aligned within their field (Req 15.3).""" panel = _init_alpha_panel() panel._snap = DataSnapshot(cat1=0.9) markup = panel._render_markup() # The value should be right-aligned (padded with spaces on the left) assert " 0.90" in markup # --------------------------------------------------------------------------- # ExtFPanel tests # Validates: Requirements 4.1, 4.2, 4.3, 12.3 # --------------------------------------------------------------------------- from dolphin_tui import ExtFPanel # noqa: E402 def _init_extf_panel() -> ExtFPanel: panel = ExtFPanel.__new__(ExtFPanel) panel._snap = None return panel def test_extf_panel_title_present(): """Panel markup always includes the ExtF title.""" panel = _init_extf_panel() markup = panel._render_markup() assert "ExtF" in markup def test_extf_panel_no_snap_shows_dashes(): """With no snapshot, all fields render as '--'.""" panel = _init_extf_panel() markup = panel._render_markup() assert "funding: --" in markup assert "dvol: --" in markup assert "fng: --" in markup assert "taker: --" in markup assert "vix: --" in markup assert "ls_btc: --" in markup def test_extf_panel_numeric_fields_formatted( ): """Numeric fields render with 4 decimal places (Req 4.1).""" panel = _init_extf_panel() panel._snap = DataSnapshot( funding_btc=-0.0123, dvol_btc=62.4, fng=28.0, taker=0.81, vix=18.2, ls_btc=0.48, ) markup = panel._render_markup() assert "funding: -0.0123" in markup assert "dvol: 62.4000" in markup assert "fng: 28.0000" in markup assert "taker: 0.8100" in markup assert "vix: 18.2000" in markup assert "ls_btc: 0.4800" in markup def test_extf_panel_acb_ready_true_green(): """acb_ready=True renders as green ✓ (Req 4.2).""" panel = _init_extf_panel() panel._snap = DataSnapshot(acb_ready=True) markup = panel._render_markup() assert "[green]✓[/green]" in markup def test_extf_panel_acb_ready_false_red(): """acb_ready=False renders as red ✗ (Req 4.2).""" panel = _init_extf_panel() panel._snap = DataSnapshot(acb_ready=False) markup = panel._render_markup() assert "[red]✗[/red]" in markup def test_extf_panel_acb_ready_none_dim_dash(): """acb_ready=None renders as dim '--' (Req 12.3).""" panel = _init_extf_panel() panel._snap = DataSnapshot(acb_ready=None) markup = panel._render_markup() assert "[dim]--[/dim]" in markup def test_extf_panel_acb_present_displayed(): """acb_present string is displayed as-is (Req 4.2).""" panel = _init_extf_panel() panel._snap = DataSnapshot(acb_present="9/9") markup = panel._render_markup() assert "9/9" in markup def test_extf_panel_acb_present_none_shows_dash(): """acb_present=None renders as '--' (Req 12.3).""" panel = _init_extf_panel() panel._snap = DataSnapshot(acb_present=None) markup = panel._render_markup() assert "ACB: --" in markup def test_extf_panel_age_fresh_green(): """Fresh age (< STALE_WARN) renders with green color (Req 4.3).""" panel = _init_extf_panel() panel._snap = DataSnapshot(exf_age_s=5.0) markup = panel._render_markup() assert "[green]5.0s ●[/green]" in markup def test_extf_panel_age_stale_yellow(): """Stale age (>= STALE_WARN, < STALE_DEAD) renders with yellow color (Req 4.3).""" panel = _init_extf_panel() panel._snap = DataSnapshot(exf_age_s=30.0) markup = panel._render_markup() assert "[yellow]30.0s ●[/yellow]" in markup def test_extf_panel_age_dead_red(): """Dead age (>= STALE_DEAD) renders with red color (Req 4.3).""" panel = _init_extf_panel() panel._snap = DataSnapshot(exf_age_s=90.0) markup = panel._render_markup() assert "[red]90.0s ●[/red]" in markup def test_extf_panel_age_none_dim_na(): """None age renders as dim N/A (Req 12.3).""" panel = _init_extf_panel() panel._snap = DataSnapshot(exf_age_s=None) markup = panel._render_markup() assert "[dim]N/A ●[/dim]" in markup def test_extf_panel_all_none_no_crash(): """All-None snapshot never crashes (Req 12.3).""" panel = _init_extf_panel() panel._snap = DataSnapshot( funding_btc=None, dvol_btc=None, fng=None, taker=None, vix=None, ls_btc=None, acb_ready=None, acb_present=None, exf_age_s=None, ) markup = panel._render_markup() assert "ExtF" in markup def test_extf_panel_update_data_stores_snap(): """update_data stores the snapshot.""" panel = _init_extf_panel() panel.update = lambda _: None snap = DataSnapshot(funding_btc=-0.012, acb_ready=True, exf_age_s=3.5) panel.update_data(snap) assert panel._snap is snap # --------------------------------------------------------------------------- # CapitalPanel tests # Validates: Requirements 7.1, 7.2, 7.3, 12.3 # --------------------------------------------------------------------------- from dolphin_tui import CapitalPanel # noqa: E402 def _init_capital_panel() -> CapitalPanel: panel = CapitalPanel.__new__(CapitalPanel) panel._snap = None return panel def test_capital_panel_title_present(): """Panel markup always includes the CAPITAL / PnL title.""" panel = _init_capital_panel() markup = panel._render_markup() assert "CAPITAL / PnL" in markup def test_capital_panel_no_snap_shows_dashes(): """With no snapshot, all fields render as '--'.""" panel = _init_capital_panel() markup = panel._render_markup() assert "Blue capital: --" in markup assert "Drawdown: --" in markup assert "Peak: --" in markup assert "Trades: --" in markup assert "Nautilus cap: --" in markup assert "Naut trades: --" in markup def test_capital_panel_capital_formatted(): """Blue capital renders as $X,XXX.XX (Req 7.1).""" panel = _init_capital_panel() panel._snap = DataSnapshot(capital=124532.10) markup = panel._render_markup() assert "Blue capital: $124,532.10" in markup def test_capital_panel_drawdown_as_percentage(): """Drawdown stored as ratio renders as percentage (Req 7.1).""" panel = _init_capital_panel() panel._snap = DataSnapshot(drawdown=-0.0321) markup = panel._render_markup() assert "Drawdown: -3.21%" in markup def test_capital_panel_peak_formatted(): """Peak capital renders as $X,XXX.XX (Req 7.1).""" panel = _init_capital_panel() panel._snap = DataSnapshot(peak_capital=128750.00) markup = panel._render_markup() assert "Peak: $128,750.00" in markup def test_capital_panel_pnl_positive_green(): """Positive PnL renders with green color (Req 7.3).""" panel = _init_capital_panel() panel._snap = DataSnapshot(pnl=1240.50) markup = panel._render_markup() assert "[green]+$1,240.50[/green]" in markup def test_capital_panel_pnl_negative_red(): """Negative PnL renders with red color (Req 7.3).""" panel = _init_capital_panel() panel._snap = DataSnapshot(pnl=-320.75) markup = panel._render_markup() assert "[red]-$320.75[/red]" in markup def test_capital_panel_pnl_zero_white(): """Zero PnL renders with white color (Req 7.3).""" panel = _init_capital_panel() panel._snap = DataSnapshot(pnl=0.0) markup = panel._render_markup() assert "[white]$0.00[/white]" in markup def test_capital_panel_pnl_none_white_dash(): """None PnL renders as white '--' (Req 12.3).""" panel = _init_capital_panel() panel._snap = DataSnapshot(pnl=None) markup = panel._render_markup() assert "[white]--[/white]" in markup def test_capital_panel_trades_displayed(): """Trades count renders as integer string (Req 7.1).""" panel = _init_capital_panel() panel._snap = DataSnapshot(trades=12) markup = panel._render_markup() assert "Trades: 12" in markup def test_capital_panel_nautilus_capital_formatted(): """Nautilus capital renders as $X,XXX.XX (Req 7.2).""" panel = _init_capital_panel() panel._snap = DataSnapshot(nautilus_capital=124532.10) markup = panel._render_markup() assert "Nautilus cap: $124,532.10" in markup def test_capital_panel_nautilus_pnl_positive_green(): """Positive Nautilus PnL renders with green color (Req 7.2, 7.3).""" panel = _init_capital_panel() panel._snap = DataSnapshot(nautilus_pnl=1240.50) markup = panel._render_markup() assert "[green]+$1,240.50[/green]" in markup def test_capital_panel_nautilus_pnl_negative_red(): """Negative Nautilus PnL renders with red color (Req 7.2, 7.3).""" panel = _init_capital_panel() panel._snap = DataSnapshot(nautilus_pnl=-500.00) markup = panel._render_markup() assert "[red]-$500.00[/red]" in markup def test_capital_panel_nautilus_trades_displayed(): """Nautilus trades count renders as integer string (Req 7.2).""" panel = _init_capital_panel() panel._snap = DataSnapshot(nautilus_trades=7) markup = panel._render_markup() assert "Naut trades: 7" in markup def test_capital_panel_nautilus_posture_apex_green(): """Nautilus APEX posture renders with green color (Req 7.2).""" panel = _init_capital_panel() panel._snap = DataSnapshot(nautilus_posture="APEX") markup = panel._render_markup() assert "[green]APEX[/green]" in markup def test_capital_panel_nautilus_posture_hibernate_red(): """Nautilus HIBERNATE posture renders with red color (Req 7.2).""" panel = _init_capital_panel() panel._snap = DataSnapshot(nautilus_posture="HIBERNATE") markup = panel._render_markup() assert "[red]HIBERNATE[/red]" in markup def test_capital_panel_nautilus_posture_none_dim_dash(): """None Nautilus posture renders as dim '--' (Req 12.3).""" panel = _init_capital_panel() panel._snap = DataSnapshot(nautilus_posture=None) markup = panel._render_markup() assert "[dim]--[/dim]" in markup def test_capital_panel_all_none_no_crash(): """All-None snapshot never crashes (Req 12.3).""" panel = _init_capital_panel() panel._snap = DataSnapshot( capital=None, drawdown=None, peak_capital=None, pnl=None, trades=None, nautilus_capital=None, nautilus_pnl=None, nautilus_trades=None, nautilus_posture=None, ) markup = panel._render_markup() assert "CAPITAL / PnL" in markup def test_capital_panel_update_data_stores_snap(): """update_data stores the snapshot.""" panel = _init_capital_panel() panel.update = lambda _: None snap = DataSnapshot(capital=100000.0, pnl=500.0) panel.update_data(snap) assert panel._snap is snap # --------------------------------------------------------------------------- # PrefectPanel tests # Validates: Requirements 8.1, 8.2, 8.3, 8.4 # --------------------------------------------------------------------------- from dolphin_tui import PrefectPanel, PREFECT_FLOW_STATUS_COLORS # noqa: E402 def _init_prefect_panel() -> PrefectPanel: panel = PrefectPanel.__new__(PrefectPanel) panel._snap = None return panel def test_prefect_panel_title_present(): """Panel markup always includes the PREFECT FLOWS title.""" panel = _init_prefect_panel() markup = panel._render_markup() assert "PREFECT FLOWS" in markup def test_prefect_panel_no_snap_shows_offline(): """With no snapshot, shows PREFECT OFFLINE in red.""" panel = _init_prefect_panel() markup = panel._render_markup() assert "[red][PREFECT OFFLINE][/red]" in markup def test_prefect_panel_healthy_true_shows_green_badge(): """prefect_healthy=True shows green [PREFECT ✓] badge (Req 8.1).""" panel = _init_prefect_panel() panel._snap = DataSnapshot(prefect_healthy=True) markup = panel._render_markup() assert "[green][PREFECT ✓][/green]" in markup def test_prefect_panel_healthy_false_shows_offline_red(): """prefect_healthy=False shows red [PREFECT OFFLINE] badge (Req 8.4).""" panel = _init_prefect_panel() panel._snap = DataSnapshot(prefect_healthy=False) markup = panel._render_markup() assert "[red][PREFECT OFFLINE][/red]" in markup def test_prefect_panel_completed_status_green(): """COMPLETED status renders with [green] markup (Req 8.3).""" panel = _init_prefect_panel() panel._snap = DataSnapshot( prefect_healthy=True, prefect_flows=[{"name": "my-flow", "status": "COMPLETED", "start_time": None, "duration": None}], ) markup = panel._render_markup() assert "[green]COMPLETED[/green]" in markup def test_prefect_panel_running_status_cyan(): """RUNNING status renders with [cyan] markup (Req 8.3).""" panel = _init_prefect_panel() panel._snap = DataSnapshot( prefect_healthy=True, prefect_flows=[{"name": "my-flow", "status": "RUNNING", "start_time": None, "duration": None}], ) markup = panel._render_markup() assert "[cyan]RUNNING[/cyan]" in markup def test_prefect_panel_failed_status_red(): """FAILED status renders with [red] markup (Req 8.3).""" panel = _init_prefect_panel() panel._snap = DataSnapshot( prefect_healthy=True, prefect_flows=[{"name": "my-flow", "status": "FAILED", "start_time": None, "duration": None}], ) markup = panel._render_markup() assert "[red]FAILED[/red]" in markup def test_prefect_panel_flow_name_displayed(): """Flow name is displayed in the panel (Req 8.2).""" panel = _init_prefect_panel() panel._snap = DataSnapshot( prefect_healthy=True, prefect_flows=[{"name": "dolphin-daily-run", "status": "COMPLETED", "start_time": None, "duration": None}], ) markup = panel._render_markup() assert "dolphin-daily-run" in markup def test_prefect_panel_duration_formatted_minutes_seconds(): """Duration of 135s formats as '2m 15s' (Req 8.2).""" from dolphin_tui import _fmt_flow_duration assert _fmt_flow_duration(135.0) == "2m 15s" def test_prefect_panel_duration_formatted_seconds_only(): """Duration of 45s formats as '45s' (Req 8.2).""" from dolphin_tui import _fmt_flow_duration assert _fmt_flow_duration(45.0) == "45s" def test_prefect_panel_duration_none_shows_dash(): """None duration formats as '--' (Req 12.3).""" from dolphin_tui import _fmt_flow_duration assert _fmt_flow_duration(None) == "--" def test_prefect_panel_start_time_formatted_hhmm_utc(): """start_time ISO string formats as HH:MM UTC (Req 8.2).""" from dolphin_tui import _fmt_flow_start result = _fmt_flow_start("2025-01-15T14:30:00Z") assert result == "14:30 UTC" def test_prefect_panel_start_time_none_shows_dash(): """None start_time formats as '--' (Req 12.3).""" from dolphin_tui import _fmt_flow_start assert _fmt_flow_start(None) == "--" def test_prefect_panel_all_none_flow_fields_no_crash(): """Flow entry with all-None fields renders without crashing (Req 12.3).""" panel = _init_prefect_panel() panel._snap = DataSnapshot( prefect_healthy=True, prefect_flows=[{"name": None, "status": None, "start_time": None, "duration": None}], ) markup = panel._render_markup() assert "PREFECT FLOWS" in markup def test_prefect_panel_update_data_stores_snap(): """update_data stores the snapshot.""" panel = _init_prefect_panel() panel.update = lambda _: None snap = DataSnapshot(prefect_healthy=True, prefect_flows=[]) panel.update_data(snap) assert panel._snap is snap def test_prefect_flow_status_colors_constant(): """PREFECT_FLOW_STATUS_COLORS has the required keys and values.""" assert PREFECT_FLOW_STATUS_COLORS["COMPLETED"] == "green" assert PREFECT_FLOW_STATUS_COLORS["RUNNING"] == "cyan" assert PREFECT_FLOW_STATUS_COLORS["FAILED"] == "red" assert PREFECT_FLOW_STATUS_COLORS["CRASHED"] == "red" assert PREFECT_FLOW_STATUS_COLORS["PENDING"] == "yellow" assert PREFECT_FLOW_STATUS_COLORS["LATE"] == "yellow" # --------------------------------------------------------------------------- # OBFPanel tests # Validates: Requirements 1.3 # --------------------------------------------------------------------------- from dolphin_tui import OBFPanel # noqa: E402 def _init_obf_panel() -> OBFPanel: panel = OBFPanel.__new__(OBFPanel) panel._snap = None return panel def test_obf_panel_title_present(): """Panel markup always includes the OBF TOP ASSETS title.""" panel = _init_obf_panel() markup = panel._render_markup() assert "OBF TOP ASSETS" in markup def test_obf_panel_empty_obf_top_shows_no_data(): """Empty obf_top shows 'No OBF data' in dim (Req 1.3).""" panel = _init_obf_panel() panel._snap = DataSnapshot(obf_top=[]) markup = panel._render_markup() assert "No OBF data" in markup def test_obf_panel_no_snap_shows_no_data(): """With no snapshot, shows 'No OBF data'.""" panel = _init_obf_panel() markup = panel._render_markup() assert "No OBF data" in markup def test_obf_panel_asset_name_displayed(): """Asset name is displayed in the panel (Req 1.3).""" panel = _init_obf_panel() panel._snap = DataSnapshot(obf_top=[ {"asset": "BTCUSDT", "imbalance": 0.18, "fill_prob": 0.75, "depth_quality": 0.90}, ]) markup = panel._render_markup() assert "BTCUSDT" in markup def test_obf_panel_imbalance_positive_sign(): """Positive imbalance shows with + sign and 2 decimals (Req 1.3).""" panel = _init_obf_panel() panel._snap = DataSnapshot(obf_top=[ {"asset": "BTCUSDT", "imbalance": 0.18, "fill_prob": 0.75, "depth_quality": 0.90}, ]) markup = panel._render_markup() assert "+0.18" in markup def test_obf_panel_imbalance_negative_sign(): """Negative imbalance shows with - sign and 2 decimals (Req 1.3).""" panel = _init_obf_panel() panel._snap = DataSnapshot(obf_top=[ {"asset": "ETHUSDT", "imbalance": -0.05, "fill_prob": 0.60, "depth_quality": 0.80}, ]) markup = panel._render_markup() assert "-0.05" in markup def test_obf_panel_fill_prob_formatted(): """fill_prob is formatted with 2 decimal places (Req 1.3).""" panel = _init_obf_panel() panel._snap = DataSnapshot(obf_top=[ {"asset": "BTCUSDT", "imbalance": 0.10, "fill_prob": 0.75, "depth_quality": 0.90}, ]) markup = panel._render_markup() assert "0.75" in markup def test_obf_panel_depth_quality_formatted(): """depth_quality is formatted with 2 decimal places (Req 1.3).""" panel = _init_obf_panel() panel._snap = DataSnapshot(obf_top=[ {"asset": "BTCUSDT", "imbalance": 0.10, "fill_prob": 0.75, "depth_quality": 0.92}, ]) markup = panel._render_markup() assert "0.92" in markup def test_obf_panel_none_fields_show_dashes(): """None fields in an entry render as '--', no crash (Req 12.3).""" panel = _init_obf_panel() panel._snap = DataSnapshot(obf_top=[ {"asset": "BTCUSDT", "imbalance": None, "fill_prob": None, "depth_quality": None}, ]) markup = panel._render_markup() assert "BTCUSDT" in markup assert "--" in markup def test_obf_panel_update_data_stores_snap(): """update_data stores the snapshot.""" panel = _init_obf_panel() panel.update = lambda _: None snap = DataSnapshot(obf_top=[{"asset": "BTCUSDT", "imbalance": 0.1, "fill_prob": 0.5, "depth_quality": 0.8}]) panel.update_data(snap) assert panel._snap is snap # --------------------------------------------------------------------------- # LogPanel tests # Validates: Requirements 9.1, 9.2, 9.3, 9.4 # --------------------------------------------------------------------------- from dolphin_tui import LogPanel # noqa: E402 def _init_log_panel() -> LogPanel: panel = LogPanel.__new__(LogPanel) panel._snap = None panel._lines = [] panel._log_path = "run_logs/meta_health.log" return panel def test_log_panel_is_verticalscroll_subclass(): """LogPanel extends VerticalScroll (Req 9.1).""" import textual.widgets as _tw assert issubclass(LogPanel, _tw.VerticalScroll) def test_log_panel_update_data_stores_snap(): """update_data stores the snapshot (Req 9.1).""" panel = _init_log_panel() snap = DataSnapshot(log_lines=["line1", "line2"]) panel.update_data(snap) assert panel._snap is snap def test_log_panel_log_lines_joined_with_newlines(): """Log lines are joined with newlines in the content (Req 9.2).""" panel = _init_log_panel() panel._lines = ["line one", "line two", "line three"] content = panel._build_content() assert "line one" in content assert "line two" in content assert "line three" in content # Lines should be separated by newlines assert content.count("\n") >= 2 def test_log_panel_empty_log_lines_renders_title(): """Empty log_lines renders the title line (Req 9.3).""" panel = _init_log_panel() panel._lines = [] content = panel._build_content() assert "LOG TAIL" in content def test_log_panel_log_not_found_passthrough(): """'Log not found' message from fetcher is displayed (Req 9.4).""" panel = _init_log_panel() snap = DataSnapshot(log_lines=["Log not found: run_logs/meta_health.log"]) panel.update_data(snap) content = panel._build_content() assert "Log not found" in content def test_log_panel_update_data_sets_lines(): """update_data populates _lines from snap.log_lines.""" panel = _init_log_panel() snap = DataSnapshot(log_lines=["alpha", "beta", "gamma"]) panel.update_data(snap) assert panel._lines == ["alpha", "beta", "gamma"] def test_log_panel_title_line_in_content(): """Panel content includes the title line with meta_health.log reference (Req 9.1).""" panel = _init_log_panel() panel._lines = ["some log line"] content = panel._build_content() assert "LOG TAIL" in content assert "meta_health.log" in content # --------------------------------------------------------------------------- # Terminal size check tests # Validates: Requirements 14.4 # --------------------------------------------------------------------------- from dolphin_tui import MIN_TERM_WIDTH, MIN_TERM_HEIGHT # noqa: E402 def test_min_term_width_is_120(): """MIN_TERM_WIDTH constant must be 120 (Req 14.4).""" assert MIN_TERM_WIDTH == 120 def test_min_term_height_is_30(): """MIN_TERM_HEIGHT constant must be 30 (Req 14.4).""" assert MIN_TERM_HEIGHT == 30 def test_terminal_too_small_width(): """Width below 120 is considered too small (Req 14.4).""" assert 119 < MIN_TERM_WIDTH def test_terminal_too_small_height(): """Height below 30 is considered too small (Req 14.4).""" assert 29 < MIN_TERM_HEIGHT def test_terminal_exact_minimum_not_too_small(): """Exactly 120×30 is not too small (Req 14.4).""" width, height = MIN_TERM_WIDTH, MIN_TERM_HEIGHT too_small = width < MIN_TERM_WIDTH or height < MIN_TERM_HEIGHT assert not too_small def test_terminal_one_below_width_is_too_small(): """119×30 is too small (Req 14.4).""" width, height = MIN_TERM_WIDTH - 1, MIN_TERM_HEIGHT too_small = width < MIN_TERM_WIDTH or height < MIN_TERM_HEIGHT assert too_small def test_terminal_one_below_height_is_too_small(): """120×29 is too small (Req 14.4).""" width, height = MIN_TERM_WIDTH, MIN_TERM_HEIGHT - 1 too_small = width < MIN_TERM_WIDTH or height < MIN_TERM_HEIGHT assert too_small def test_terminal_both_below_minimum_is_too_small(): """80×24 is too small (Req 14.4).""" width, height = 80, 24 too_small = width < MIN_TERM_WIDTH or height < MIN_TERM_HEIGHT assert too_small def test_terminal_large_size_not_too_small(): """200×50 is not too small (Req 14.4).""" width, height = 200, 50 too_small = width < MIN_TERM_WIDTH or height < MIN_TERM_HEIGHT assert not too_small def test_warning_message_contains_minimum_dimensions(): """Warning message text includes the minimum dimensions (Req 14.4).""" # Simulate the warning text that _check_terminal_size() would produce width, height = 80, 24 msg = ( f"⚠ Terminal too small — resize to " f"{MIN_TERM_WIDTH}×{MIN_TERM_HEIGHT} minimum\n" f"(current: {width}×{height})" ) assert "120" in msg assert "30" in msg assert "80" in msg assert "24" in msg assert "Terminal too small" in msg # =========================================================================== # Task 6.1 — HZ Offline Tests # Validates: Requirements 11.2, 12.1, 12.3 # =========================================================================== import asyncio from unittest.mock import MagicMock, patch, AsyncMock from dolphin_tui import ( # noqa: E402 DataSnapshot, DolphinDataFetcher, HeaderBar, SystemHealthPanel, AlphaEnginePanel, ScanBridgePanel, ExtFPanel, EsoFPanel, CapitalPanel, PrefectPanel, OBFPanel, LogPanel, ) # --------------------------------------------------------------------------- # Helper: build a fully-None HZ DataSnapshot (hz_connected=False) # --------------------------------------------------------------------------- def _offline_snapshot(**overrides) -> DataSnapshot: """Return a DataSnapshot with hz_connected=False and all HZ fields None.""" return DataSnapshot( hz_connected=False, prefect_connected=False, prefect_healthy=False, prefect_flows=[], log_lines=[], # All HZ-derived fields are None by default (DataSnapshot defaults) **overrides, ) # --------------------------------------------------------------------------- # 6.1a — HeaderBar shows [HZ ✗] when hz_connected=False # Validates: Requirements 11.2 # --------------------------------------------------------------------------- def test_header_hz_disconnected_badge(): """HeaderBar renders [HZ ✗] in red when hz_connected=False (Req 11.2).""" header = HeaderBar.__new__(HeaderBar) header._hz_connected = False header._meta_status = None markup = header._render_markup() assert "[red][HZ ✗][/red]" in markup assert "[green][HZ ✓][/green]" not in markup def test_header_hz_connected_badge(): """HeaderBar renders [HZ ✓] in green when hz_connected=True (Req 11.2).""" header = HeaderBar.__new__(HeaderBar) header._hz_connected = True header._meta_status = None markup = header._render_markup() assert "[green][HZ ✓][/green]" in markup assert "[red][HZ ✗][/red]" not in markup def test_header_update_status_disconnected(): """update_status(False, None) sets _hz_connected=False and renders [HZ ✗].""" header = HeaderBar.__new__(HeaderBar) header._hz_connected = True header._meta_status = None # Stub update() so it doesn't call Textual internals header.update = lambda _: None header.update_status(False, None) assert header._hz_connected is False markup = header._render_markup() assert "[red][HZ ✗][/red]" in markup # --------------------------------------------------------------------------- # 6.1b — DolphinDataFetcher.fetch() returns hz_connected=False when HZ unreachable # Validates: Requirements 12.1 # --------------------------------------------------------------------------- def test_fetcher_fetch_returns_snapshot_when_hz_none(): """fetch() returns DataSnapshot(hz_connected=False) when hz_client is None.""" fetcher = DolphinDataFetcher.__new__(DolphinDataFetcher) fetcher.hz_client = None fetcher.hz_connected = False fetcher._running = True fetcher._reconnect_task = None fetcher._reconnect_backoff = 5.0 fetcher._reconnect_backoff_initial = 5.0 fetcher._reconnect_backoff_max = 60.0 fetcher._reconnect_backoff_multiplier = 1.5 fetcher.log_path = "run_logs/meta_health.log" # Stub _start_reconnect to be a no-op fetcher._start_reconnect = lambda: None # Stub fetch_prefect to return (False, []) async def _fake_prefect(): return (False, []) fetcher.fetch_prefect = _fake_prefect # Stub tail_log to return [] fetcher.tail_log = lambda path, n=50: [] snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert isinstance(snap, DataSnapshot) assert snap.hz_connected is False # All HZ-derived fields must be None assert snap.scan_number is None assert snap.vel_div is None assert snap.posture is None assert snap.rm is None assert snap.capital is None assert snap.meta_rm is None assert snap.meta_status is None def test_fetcher_connect_hz_failure_sets_disconnected(): """connect_hz() returns False and sets hz_connected=False when HZ raises.""" fetcher = DolphinDataFetcher(hz_host="127.0.0.1", hz_port=9999) fetcher._start_reconnect = lambda: None # suppress background task with patch("dolphin_tui.HAZELCAST_AVAILABLE", True): with patch("dolphin_tui.hazelcast") as mock_hz: mock_hz.HazelcastClient.side_effect = ConnectionRefusedError("refused") result = asyncio.get_event_loop().run_until_complete(fetcher.connect_hz()) assert result is False assert fetcher.hz_connected is False assert fetcher.hz_client is None # --------------------------------------------------------------------------- # 6.1c — _apply_snapshot() does not raise with all-None snapshot # Validates: Requirements 12.1, 12.3 # --------------------------------------------------------------------------- def test_apply_snapshot_all_none_no_crash(): """_apply_snapshot() with all-None HZ fields never raises (Req 12.1, 12.3).""" snap = _offline_snapshot() # Exercise every panel's _render_markup() directly (no Textual app needed) panels = [ _make_system_health_panel(snap), _make_alpha_panel(snap), _make_scan_bridge_panel(snap), _make_extf_panel(snap), _make_esof_panel(snap), _make_capital_panel(snap), _make_prefect_panel(snap), _make_obf_panel(snap), ] for panel in panels: markup = panel._render_markup() assert markup # non-empty string, no exception def _make_system_health_panel(snap: DataSnapshot) -> SystemHealthPanel: p = SystemHealthPanel.__new__(SystemHealthPanel) p._snap = snap return p def _make_alpha_panel(snap: DataSnapshot) -> AlphaEnginePanel: p = AlphaEnginePanel.__new__(AlphaEnginePanel) p._snap = snap return p def _make_scan_bridge_panel(snap: DataSnapshot) -> ScanBridgePanel: p = ScanBridgePanel.__new__(ScanBridgePanel) p._snap = snap return p def _make_extf_panel(snap: DataSnapshot) -> ExtFPanel: p = ExtFPanel.__new__(ExtFPanel) p._snap = snap return p def _make_esof_panel(snap: DataSnapshot) -> EsoFPanel: p = EsoFPanel.__new__(EsoFPanel) p._snap = snap return p def _make_capital_panel(snap: DataSnapshot) -> CapitalPanel: p = CapitalPanel.__new__(CapitalPanel) p._snap = snap return p def _make_prefect_panel(snap: DataSnapshot) -> PrefectPanel: p = PrefectPanel.__new__(PrefectPanel) p._snap = snap return p def _make_obf_panel(snap: DataSnapshot) -> OBFPanel: p = OBFPanel.__new__(OBFPanel) p._snap = snap return p # --------------------------------------------------------------------------- # 6.1d — All panels render "--" (not real data) when hz_connected=False # Validates: Requirements 12.1, 12.3 # --------------------------------------------------------------------------- def test_system_health_panel_offline_shows_dashes(): """SystemHealthPanel shows '--' for all HZ fields when offline (Req 12.3).""" snap = _offline_snapshot() p = _make_system_health_panel(snap) markup = p._render_markup() assert "rm_meta: --" in markup assert "M1: --" in markup assert "M5: --" in markup assert "● --" in markup def test_alpha_panel_offline_shows_dashes(): """AlphaEnginePanel shows '--' for all HZ fields when offline (Req 12.3).""" snap = _offline_snapshot() p = _make_alpha_panel(snap) markup = p._render_markup() assert "[dim]--[/dim]" in markup # posture assert "Rm: --" in markup def test_scan_bridge_panel_offline_shows_dashes(): """ScanBridgePanel shows '--' for all HZ fields when offline (Req 12.3).""" snap = _offline_snapshot() p = _make_scan_bridge_panel(snap) markup = p._render_markup() assert "Scan #--" in markup assert "vel_div: --" in markup assert "bridge_ts: --" in markup def test_extf_panel_offline_shows_dashes(): """ExtFPanel shows '--' for all HZ fields when offline (Req 12.3).""" snap = _offline_snapshot() p = _make_extf_panel(snap) markup = p._render_markup() assert "funding: --" in markup assert "dvol: --" in markup assert "fng: --" in markup def test_esof_panel_offline_shows_dashes(): """EsoFPanel shows '--' for all HZ fields when offline (Req 12.3).""" snap = _offline_snapshot() p = _make_esof_panel(snap) markup = p._render_markup() assert "Moon: --" in markup assert "Session: --" in markup assert "MC pos: --" in markup def test_capital_panel_offline_shows_dashes(): """CapitalPanel shows '--' for all HZ fields when offline (Req 12.3).""" snap = _offline_snapshot() p = _make_capital_panel(snap) markup = p._render_markup() assert "Blue capital: --" in markup assert "Drawdown: --" in markup assert "Nautilus cap: --" in markup def test_prefect_panel_offline_shows_offline(): """PrefectPanel shows PREFECT OFFLINE when prefect_healthy=False (Req 8.4).""" snap = _offline_snapshot() p = _make_prefect_panel(snap) markup = p._render_markup() assert "[red][PREFECT OFFLINE][/red]" in markup def test_obf_panel_offline_shows_no_data(): """OBFPanel shows 'No OBF data' when obf_top is empty (Req 1.3).""" snap = _offline_snapshot() p = _make_obf_panel(snap) markup = p._render_markup() assert "No OBF data" in markup # --------------------------------------------------------------------------- # 6.1e — Header shows [HZ ✗] for offline snapshot # Validates: Requirements 11.2 # --------------------------------------------------------------------------- def test_header_offline_snapshot_shows_hz_cross(): """HeaderBar shows [HZ ✗] when snapshot has hz_connected=False (Req 11.2).""" snap = _offline_snapshot() header = HeaderBar.__new__(HeaderBar) header._hz_connected = snap.hz_connected header._meta_status = snap.meta_status markup = header._render_markup() assert "[red][HZ ✗][/red]" in markup # --------------------------------------------------------------------------- # 6.1f — Property-based test: _apply_snapshot never raises for any all-None snapshot # Validates: Requirements 12.1, 12.3 # Property: for any DataSnapshot with hz_connected=False and all-None HZ fields, # every panel's _render_markup() never raises and returns a non-empty string. # --------------------------------------------------------------------------- from hypothesis import given, settings # noqa: E402 (already imported above, re-import is fine) import hypothesis.strategies as st # noqa: E402 @given( ts=st.floats(min_value=0.0, max_value=1e12, allow_nan=False, allow_infinity=False), prefect_healthy=st.booleans(), log_lines=st.lists(st.text(max_size=80), max_size=10), ) @settings(max_examples=200) def test_apply_snapshot_never_raises_property( ts: float, prefect_healthy: bool, log_lines: list, ) -> None: """**Validates: Requirements 12.1, 12.3** Property: for any DataSnapshot with hz_connected=False and all HZ-derived fields at their None defaults, every panel's _render_markup() never raises and always returns a non-empty string. """ snap = DataSnapshot( ts=ts, hz_connected=False, prefect_connected=prefect_healthy, prefect_healthy=prefect_healthy, prefect_flows=[], log_lines=log_lines, # All HZ fields remain None (DataSnapshot defaults) ) panels = [ _make_system_health_panel(snap), _make_alpha_panel(snap), _make_scan_bridge_panel(snap), _make_extf_panel(snap), _make_esof_panel(snap), _make_capital_panel(snap), _make_prefect_panel(snap), _make_obf_panel(snap), ] for panel in panels: result = panel._render_markup() assert isinstance(result, str) assert len(result) > 0 # Header also must not raise header = HeaderBar.__new__(HeaderBar) header._hz_connected = False header._meta_status = None header_markup = header._render_markup() assert "[red][HZ ✗][/red]" in header_markup # =========================================================================== # Task 6.2 — HZ Online Tests # Validates: Requirements 1.2, 2.1–2.4, 3.1–3.4, 4.1–4.3, 5.1–5.2, # 6.1–6.3, 7.1–7.3, 11.2, 13.1 # =========================================================================== import time as _time_module # noqa: E402 (time already imported at module level) import json as _json_module # noqa: E402 (json already imported at module level) # --------------------------------------------------------------------------- # Fixture JSON payloads # --------------------------------------------------------------------------- FIXTURE_EIGEN_SCAN = _json_module.dumps({ "scan_number": 8634, "vel_div": -0.0312, "w50_velocity": -0.0421, "w750_velocity": -0.0109, "instability_50": 0.0234, "asset_prices": {"BTCUSDT": 65000.0, "ETHUSDT": 3200.0}, "bridge_ts": "2026-04-02T10:30:00+00:00", }) FIXTURE_SAFETY = _json_module.dumps({ "posture": "APEX", "Rm": 0.82, "Cat1": 0.9, "Cat2": 0.8, "Cat3": 0.7, "Cat4": 1.0, "Cat5": 0.9, }) FIXTURE_STATE_LATEST = _json_module.dumps({ "capital": 124532.10, "drawdown": -0.0321, "peak_capital": 128750.00, "pnl": 1240.50, "trades": 12, }) FIXTURE_STATE_NAUTILUS = _json_module.dumps({ "capital": 124532.10, "pnl": 1240.50, "trades": 7, "posture": "APEX", "param_hash": "abc123", }) FIXTURE_EXF = _json_module.dumps({ "funding_btc": -0.0123, "dvol_btc": 62.4, "fng": 28.0, "taker": 0.81, "vix": 18.2, "ls_btc": 0.48, "_acb_ready": True, "_acb_present": "9/9", "_pushed_at": "2026-04-02T10:29:55+00:00", }) FIXTURE_ESOF = _json_module.dumps({ "moon_phase_name": "Waxing Gibbous", "mercury_retrograde": False, "liquidity_session": "London", "market_cycle_position": 0.42, "_pushed_at": "2026-04-02T10:29:50+00:00", }) FIXTURE_META_HEALTH = _json_module.dumps({ "rm_meta": 0.923, "status": "GREEN", "m1_proc": 1.0, "m2_heartbeat": 1.0, "m3_data_freshness": 1.0, "m4_control_plane": 1.0, "m5_coherence": 1.0, }) FIXTURE_HEARTBEAT = _json_module.dumps({ "ts": _time_module.time() - 2.0, "phase": "RUNNING", "flow": "nautilus_prefect", }) # --------------------------------------------------------------------------- # Helper: build a MagicMock HZ client returning fixture data # --------------------------------------------------------------------------- def _make_mock_hz_client(): """Build a MagicMock HZ client that returns fixture data for all known maps/keys.""" _data = { ("DOLPHIN_FEATURES", "latest_eigen_scan"): FIXTURE_EIGEN_SCAN, ("DOLPHIN_FEATURES", "acb_boost"): _json_module.dumps({"boost": 1.55, "beta": 0.80}), ("DOLPHIN_FEATURES", "exf_latest"): FIXTURE_EXF, ("DOLPHIN_FEATURES", "esof_latest"): FIXTURE_ESOF, ("DOLPHIN_SAFETY", "latest"): FIXTURE_SAFETY, ("DOLPHIN_STATE_BLUE", "latest"): FIXTURE_STATE_LATEST, ("DOLPHIN_STATE_BLUE", "latest_nautilus"): FIXTURE_STATE_NAUTILUS, ("DOLPHIN_HEARTBEAT", "nautilus_flow_heartbeat"): FIXTURE_HEARTBEAT, ("DOLPHIN_META_HEALTH", "latest"): FIXTURE_META_HEALTH, } def _make_imap(map_name): imap = MagicMock() def _get(key): future = MagicMock() future.result.return_value = _data.get((map_name, key)) return future imap.get.side_effect = _get # key_set for OBF shards — return empty ks = MagicMock() ks.result.return_value = [] imap.key_set.return_value = ks return imap client = MagicMock() def _get_map(name): future = MagicMock() future.result.return_value = _make_imap(name) return future client.get_map.side_effect = _get_map return client # --------------------------------------------------------------------------- # Helper: build a DolphinDataFetcher wired to the mock HZ client # --------------------------------------------------------------------------- def _make_online_fetcher() -> DolphinDataFetcher: fetcher = DolphinDataFetcher.__new__(DolphinDataFetcher) fetcher.hz_host = "dolphin.taile8ad92.ts.net" fetcher.hz_port = 5701 fetcher.hz_client = _make_mock_hz_client() fetcher.hz_connected = True fetcher._running = True fetcher._reconnect_task = None fetcher._reconnect_backoff = 5.0 fetcher._reconnect_backoff_initial = 5.0 fetcher._reconnect_backoff_max = 60.0 fetcher._reconnect_backoff_multiplier = 1.5 fetcher.log_path = "run_logs/meta_health.log" fetcher._start_reconnect = lambda: None async def _fake_prefect(): return (True, [{"name": "test-flow", "status": "COMPLETED", "start_time": None, "duration": 60.0}]) fetcher.fetch_prefect = _fake_prefect fetcher.tail_log = lambda path, n=50: ["10:30:01 [INFO] RM_META=0.923 [GREEN]"] return fetcher # --------------------------------------------------------------------------- # 6.2a — fetch() with mock HZ returns hz_connected=True # Validates: Requirements 1.2, 11.2 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_returns_connected_snapshot(): """fetch() with mock HZ client returns DataSnapshot(hz_connected=True).""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert isinstance(snap, DataSnapshot) assert snap.hz_connected is True # --------------------------------------------------------------------------- # 6.2b — Scan fields populated from fixture # Validates: Requirements 2.1, 2.3 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_scan_fields_populated(): """fetch() populates scan_number, vel_div, w50_velocity, w750_velocity, instability_50.""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert snap.scan_number == 8634 assert snap.vel_div is not None assert snap.w50_velocity is not None assert snap.w750_velocity is not None assert snap.instability_50 is not None # --------------------------------------------------------------------------- # 6.2c — Safety fields populated from fixture # Validates: Requirements 3.1, 3.2, 3.4 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_safety_fields_populated(): """fetch() populates posture, rm, and cat1-cat5 from DOLPHIN_SAFETY.""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert snap.posture == "APEX" assert snap.rm == 0.82 assert snap.cat1 is not None assert snap.cat2 is not None assert snap.cat3 is not None assert snap.cat4 is not None assert snap.cat5 is not None # --------------------------------------------------------------------------- # 6.2d — State (Blue) fields populated from fixture # Validates: Requirements 7.1 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_state_fields_populated(): """fetch() populates capital, drawdown, peak_capital, pnl, trades from DOLPHIN_STATE_BLUE.""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert snap.capital is not None assert snap.drawdown is not None assert snap.peak_capital is not None assert snap.pnl is not None assert snap.trades is not None assert abs(snap.capital - 124532.10) < 0.01 assert abs(snap.drawdown - (-0.0321)) < 1e-6 assert snap.trades == 12 # --------------------------------------------------------------------------- # 6.2e — Nautilus state fields populated from fixture # Validates: Requirements 7.2 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_nautilus_fields_populated(): """fetch() populates nautilus_capital, nautilus_pnl, nautilus_trades, nautilus_posture.""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert snap.nautilus_capital is not None assert snap.nautilus_pnl is not None assert snap.nautilus_trades is not None assert snap.nautilus_posture is not None # --------------------------------------------------------------------------- # 6.2f — ExtF fields populated from fixture # Validates: Requirements 4.1, 4.2, 4.3 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_extf_fields_populated(): """fetch() populates all ExtF fields including acb_ready, acb_present, exf_age_s.""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert snap.funding_btc is not None assert snap.dvol_btc is not None assert snap.fng is not None assert snap.taker is not None assert snap.vix is not None assert snap.ls_btc is not None assert snap.acb_ready is not None assert snap.acb_present is not None assert snap.exf_age_s is not None # --------------------------------------------------------------------------- # 6.2g — EsoF fields populated from fixture # Validates: Requirements 5.1, 5.2 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_esof_fields_populated(): """fetch() populates moon_phase, mercury_retro, liquidity_session, market_cycle_pos, esof_age_s.""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert snap.moon_phase is not None assert snap.mercury_retro is not None assert snap.liquidity_session is not None assert snap.market_cycle_pos is not None assert snap.esof_age_s is not None # --------------------------------------------------------------------------- # 6.2h — Meta health fields populated from fixture # Validates: Requirements 6.1, 6.2, 6.3 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_meta_health_populated(): """fetch() populates meta_rm, meta_status, and M1-M5 from DOLPHIN_META_HEALTH.""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert snap.meta_rm is not None assert abs(snap.meta_rm - 0.923) < 1e-6 assert snap.meta_status == "GREEN" assert snap.m1_proc is not None assert snap.m2_heartbeat is not None assert snap.m3_data is not None assert snap.m4_cp is not None assert snap.m5_coh is not None # --------------------------------------------------------------------------- # 6.2i — Heartbeat fields populated from fixture # Validates: Requirements 1.2 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_heartbeat_populated(): """fetch() populates heartbeat_ts, heartbeat_phase, heartbeat_flow, heartbeat_age_s >= 0.""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert snap.heartbeat_ts is not None assert snap.heartbeat_phase is not None assert snap.heartbeat_flow is not None assert snap.heartbeat_age_s is not None assert snap.heartbeat_age_s >= 0 # --------------------------------------------------------------------------- # 6.2j — Scan age computed from bridge_ts # Validates: Requirements 2.2, 2.4 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_scan_age_computed(): """fetch() computes scan_age_s as a non-negative float from bridge_ts.""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert snap.scan_age_s is not None assert snap.scan_age_s >= 0 # --------------------------------------------------------------------------- # 6.2k — All panels render real data (not "--") for key fields # Validates: Requirements 1.2, 2.1, 3.1, 6.1, 7.1 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_all_panels_render_real_data(): """With a populated snapshot, panels render real values not '--' for key fields.""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) scan_panel = _make_scan_bridge_panel(snap) scan_markup = scan_panel._render_markup() assert "8634" in scan_markup alpha_panel = _make_alpha_panel(snap) alpha_markup = alpha_panel._render_markup() assert "APEX" in alpha_markup assert "0.82" in alpha_markup capital_panel = _make_capital_panel(snap) capital_markup = capital_panel._render_markup() assert "124,532.10" in capital_markup health_panel = _make_system_health_panel(snap) health_markup = health_panel._render_markup() assert "GREEN" in health_markup # --------------------------------------------------------------------------- # 6.2l — HeaderBar shows [HZ ✓] when hz_connected=True # Validates: Requirements 11.2 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_header_shows_hz_tick(): """HeaderBar with hz_connected=True shows [green][HZ ✓][/green].""" fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) header = HeaderBar.__new__(HeaderBar) header._hz_connected = snap.hz_connected header._meta_status = snap.meta_status markup = header._render_markup() assert "[green][HZ ✓][/green]" in markup assert "[red][HZ ✗][/red]" not in markup # --------------------------------------------------------------------------- # 6.2m — fetch() completes within 1.5s (Req 13.1) # Validates: Requirements 13.1 # --------------------------------------------------------------------------- def test_fetcher_fetch_hz_online_completes_within_poll_cycle(): """fetch() with mock HZ completes in under 1.5 seconds (Req 13.1).""" fetcher = _make_online_fetcher() t0 = _time_module.time() asyncio.get_event_loop().run_until_complete(fetcher.fetch()) elapsed = _time_module.time() - t0 assert elapsed < 1.5, f"fetch() took {elapsed:.3f}s, expected < 1.5s" # --------------------------------------------------------------------------- # 6.2n — Property-based test: key fields never None with valid fixture data # Validates: Requirements 1.2, 2.1, 3.1, 6.1, 7.1 # Property: given valid fixture data (deterministic mock), fetch() always returns # a snapshot where scan_number, posture, rm, capital, meta_rm, meta_status # are non-None. # --------------------------------------------------------------------------- @given(st.just(None)) @settings(max_examples=10) def test_fetch_hz_online_snapshot_fields_never_none_property(_: None) -> None: """**Validates: Requirements 1.2, 2.1, 3.1, 6.1, 7.1** Property: with valid fixture data, fetch() always returns a snapshot where the key fields (scan_number, posture, rm, capital, meta_rm, meta_status) are non-None across 10 repeated calls (confirms stability of the mock). """ fetcher = _make_online_fetcher() snap = asyncio.get_event_loop().run_until_complete(fetcher.fetch()) assert snap.hz_connected is True assert snap.scan_number is not None assert snap.posture is not None assert snap.rm is not None assert snap.capital is not None assert snap.meta_rm is not None assert snap.meta_status is not None