Files
DOLPHIN/Observability/TUI/test_dolphin_tui.py
hjnormey 01c19662cb initial: import DOLPHIN baseline 2026-04-21 from dolphinng5_predict working tree
Includes core prod + GREEN/BLUE subsystems:
- prod/ (BLUE harness, configs, scripts, docs)
- nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved)
- adaptive_exit/ (AEM engine + models/bucket_assignments.pkl)
- Observability/ (EsoF advisor, TUI, dashboards)
- external_factors/ (EsoF producer)
- mc_forewarning_qlabs_fork/ (MC regime/envelope)

Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
2026-04-21 16:58:38 +02:00

2071 lines
70 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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.12.4, 3.13.4, 4.14.3, 5.15.2,
# 6.16.3, 7.17.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