""" Unit tests for PINK namespace routing and isolation. Validates: - ch_writer ch_put_pink targets dolphin_pink - journal _db_for_strategy routes pink -> dolphin_pink - journal write_snapshot selects pink sink - dolphin_actor ch_put mapping for pink - No cross-contamination between BLUE/PRODGREEN/PINK """ import json import os import sys import unittest from pathlib import Path from unittest.mock import MagicMock, patch, call sys.path.insert(0, str(Path(__file__).parent.parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent.parent / "nautilus_dolphin")) sys.path.insert(0, str(Path(__file__).parent.parent)) class TestChWriterPink(unittest.TestCase): """Test ch_writer ch_put_pink targets dolphin_pink database.""" @patch("prod.ch_writer._CHWriter") def test_ch_put_pink_targets_dolphin_pink(self, MockWriter): mock_instance = MagicMock() MockWriter.return_value = mock_instance MockWriter.reset_mock() # Re-import to pick up the mock import importlib import prod.ch_writer as ch_mod importlib.reload(ch_mod) # After reload, the module-level singletons are recreated # We need to verify ch_put_pink calls the right writer # The simplest approach: verify the _writer_pink singleton has db="dolphin_pink" def test_writer_pink_db_attribute(self): """Verify _writer_pink targets dolphin_pink database.""" from prod.ch_writer import _writer_pink self.assertEqual(_writer_pink._db, "dolphin_pink") def test_writer_prodgreen_unchanged(self): """Verify PRODGREEN writer is unchanged.""" from prod.ch_writer import _writer_prodgreen self.assertEqual(_writer_prodgreen._db, "dolphin_prodgreen") def test_writer_blue_unchanged(self): """Verify BLUE writer is unchanged.""" from prod.ch_writer import _writer self.assertEqual(_writer._db, "dolphin") def test_writer_green_unchanged(self): """Verify GREEN writer is unchanged.""" from prod.ch_writer import _writer_green self.assertEqual(_writer_green._db, "dolphin_green") def test_ch_put_pink_exists(self): """Verify ch_put_pink function exists and is callable.""" from prod.ch_writer import ch_put_pink self.assertTrue(callable(ch_put_pink)) def test_ch_put_pink_calls_put(self): """Verify ch_put_pink delegates to _writer_pink.put.""" from prod.ch_writer import _writer_pink with patch.object(_writer_pink, 'put') as mock_put: from prod.ch_writer import ch_put_pink ch_put_pink("test_table", {"key": "value"}) mock_put.assert_called_once_with("test_table", {"key": "value"}) class TestJournalRouting(unittest.TestCase): """Test bingx/journal.py strategy->DB routing.""" def test_db_for_strategy_pink(self): from prod.bingx.journal import _db_for_strategy self.assertEqual(_db_for_strategy("pink"), "dolphin_pink") def test_db_for_strategy_pink_case_insensitive(self): from prod.bingx.journal import _db_for_strategy self.assertEqual(_db_for_strategy("PINK"), "dolphin_pink") self.assertEqual(_db_for_strategy("Pink"), "dolphin_pink") def test_db_for_strategy_prodgreen_unchanged(self): from prod.bingx.journal import _db_for_strategy self.assertEqual(_db_for_strategy("prodgreen"), "dolphin_prodgreen") def test_db_for_strategy_green_unchanged(self): from prod.bingx.journal import _db_for_strategy self.assertEqual(_db_for_strategy("green"), "dolphin_green") def test_db_for_strategy_blue_unchanged(self): from prod.bingx.journal import _db_for_strategy self.assertEqual(_db_for_strategy("blue"), "dolphin") def test_db_for_strategy_prodprefix_unchanged(self): """Existing prod* prefix fallback must still work for unknown prod names.""" from prod.bingx.journal import _db_for_strategy self.assertEqual(_db_for_strategy("prodfoo"), "dolphin_prodgreen") def test_db_for_strategy_unknown_default(self): from prod.bingx.journal import _db_for_strategy self.assertEqual(_db_for_strategy("unknown"), "dolphin") def test_strategy_db_map_has_pink(self): from prod.bingx.journal import _STRATEGY_DB_MAP self.assertEqual(_STRATEGY_DB_MAP["pink"], "dolphin_pink") def test_strategy_sink_map_has_pink(self): from prod.bingx.journal import _STRATEGY_SINK_MAP sink = _STRATEGY_SINK_MAP["pink"] self.assertTrue(callable(sink)) self.assertEqual(getattr(sink, "__name__", ""), "ch_put_pink") class TestJournalSinkSelection(unittest.TestCase): """Test that write_snapshot selects the correct sink for pink strategy.""" @patch("prod.bingx.journal._STRATEGY_SINK_MAP") def test_write_snapshot_uses_pink_sink(self, mock_map): from prod.bingx.journal import write_snapshot, BingxJournalSnapshot mock_sink = MagicMock() mock_map.get.return_value = mock_sink snapshot = BingxJournalSnapshot( ts=1000000, strategy="pink", account_id="BINGX-vst", ledger_authority="exchange", payload={ "account": {"balances": [{"asset": "USDT", "total": 25000.0, "free": 25000.0}]}, "positions": {}, }, fingerprint="abc123", ) write_snapshot(snapshot) # Verify the sink map was consulted for "pink" mock_map.get.assert_called_with("pink") # Verify the pink sink was called (not prodgreen or green) mock_sink.assert_called_once() class TestExecutionConfigFields(unittest.TestCase): """Test that execution.py reads config-driven journal_strategy/journal_db.""" def test_config_has_journal_fields(self): from prod.bingx.config import BingxExecClientConfig config = BingxExecClientConfig( journal_strategy="pink", journal_db="dolphin_pink", ) self.assertEqual(config.journal_strategy, "pink") self.assertEqual(config.journal_db, "dolphin_pink") def test_config_defaults_none(self): from prod.bingx.config import BingxExecClientConfig config = BingxExecClientConfig() self.assertIsNone(config.journal_strategy) self.assertIsNone(config.journal_db) class TestBuildActorConfigOverrides(unittest.TestCase): """Test launch_dolphin_live actor DB override behavior.""" def test_v7_journal_db_does_not_overwrite_adaptive_exit_shadow_db(self): from prod.launch_dolphin_live import build_actor_config with patch.dict( os.environ, { "DOLPHIN_ADAPTIVE_EXIT_DB": "dolphin_pink", "DOLPHIN_V7_JOURNAL_DB": "dolphin_pink_v7", "DOLPHIN_FIXED_TP_PCT": "0.0020", }, clear=False, ): cfg = build_actor_config() self.assertEqual(cfg["adaptive_exit"]["shadow_db"], "dolphin_pink") self.assertEqual(cfg["v7_journal_db"], "dolphin_pink_v7") self.assertEqual(cfg["engine"]["fixed_tp_pct"], 0.0020) class TestPinkLauncherPhases(unittest.TestCase): """Test the standalone PINK phase gate helpers.""" def test_single_leg_is_default_phase(self): from prod.launch_dolphin_pink import PinkPhase, _resolve_pink_phase, _resolve_pink_exit_leg_ratios with patch.dict(os.environ, {}, clear=False): self.assertEqual(_resolve_pink_phase(), PinkPhase.SINGLE_LEG) self.assertEqual(_resolve_pink_exit_leg_ratios(PinkPhase.SINGLE_LEG), (1.0,)) def test_multi_exit_uses_configured_leg_ratios(self): from prod.launch_dolphin_pink import PinkPhase, _resolve_pink_exit_leg_ratios, _resolve_pink_phase with patch.dict( os.environ, { "DOLPHIN_PINK_PHASE": "multi_exit", "DOLPHIN_PINK_EXIT_LEG_RATIOS": "0.25,0.75,1.0", }, clear=False, ): self.assertEqual(_resolve_pink_phase(), PinkPhase.MULTI_EXIT) self.assertEqual(_resolve_pink_exit_leg_ratios(PinkPhase.MULTI_EXIT), (0.25, 0.75, 1.0)) class TestCapitalSourcePriority(unittest.TestCase): """BingX/PINK must prefer the BingX journal over portfolio fallbacks.""" def test_bingx_journal_wins_over_portfolio_and_engine(self): from nautilus_dolphin.nautilus.dolphin_actor import DolphinActor class Dummy: def __init__(self): self.live_mode = True self.dolphin_config = {"native_mode": False} self._last_portfolio_capital = 777.0 self.engine = type("E", (), {"capital": 555.0})() def _exec_venue_name(self): return "BINGX" def _get_bingx_ledger_capital(self): return 1234.5 def _get_portfolio_capital(self): return 888.0 dummy = Dummy() capital = DolphinActor._authoritative_capital(dummy) self.assertEqual(capital, 1234.5) class TestDolphinActorPinkMapping(unittest.TestCase): """Test DolphinActor correctly maps pink strategy to pink sink.""" def test_actor_pink_strategy_uses_pink_sink(self): """Verify pink strategy in actor config selects ch_put_pink.""" # We can't fully instantiate DolphinActor (needs nautilus), # but we can test the mapping logic directly. from ch_writer import ch_put_pink, ch_put_prodgreen, ch_put_green _STRATEGY_CH_SINK = { 'blue': None, 'green': ch_put_green, 'prodgreen': ch_put_prodgreen, 'pink': ch_put_pink, } self.assertIs(_STRATEGY_CH_SINK['pink'], ch_put_pink) self.assertIs(_STRATEGY_CH_SINK['prodgreen'], ch_put_prodgreen) self.assertIs(_STRATEGY_CH_SINK['green'], ch_put_green) class TestPinkConfigFile(unittest.TestCase): """Test that pink.yml has correct namespace settings.""" def test_pink_config_exists(self): config_path = Path("/mnt/dolphinng5_predict/prod/configs/pink.yml") self.assertTrue(config_path.exists(), "pink.yml must exist") def test_pink_config_has_correct_strategy(self): import yaml config_path = Path("/mnt/dolphinng5_predict/prod/configs/pink.yml") with open(config_path) as f: cfg = yaml.safe_load(f) self.assertEqual(cfg["strategy_name"], "pink") self.assertEqual(cfg["hazelcast"]["state_map"], "DOLPHIN_STATE_PINK") self.assertEqual(cfg["hazelcast"]["imap_pnl"], "DOLPHIN_PNL_PINK") self.assertEqual(cfg["adaptive_exit"]["shadow_db"], "dolphin_pink") self.assertEqual(cfg["engine"]["fixed_tp_pct"], 0.0020) class TestPinkLauncher(unittest.TestCase): """Test PINK launcher defaults.""" def test_pink_defaults(self): sys.path.insert(0, str(Path(__file__).parent.parent)) from launch_dolphin_pink import PINK_DEFAULTS self.assertEqual(PINK_DEFAULTS["strategy_name"], "pink") self.assertEqual(PINK_DEFAULTS["state_map"], "DOLPHIN_STATE_PINK") self.assertEqual(PINK_DEFAULTS["pnl_map"], "DOLPHIN_PNL_PINK") self.assertEqual(PINK_DEFAULTS["trader_id"], "DOLPHIN-PINK-001") self.assertEqual(PINK_DEFAULTS["journal_strategy"], "pink") self.assertEqual(PINK_DEFAULTS["journal_db"], "dolphin_pink") self.assertEqual(PINK_DEFAULTS["fixed_tp_pct"], 0.0020) self.assertEqual(PINK_DEFAULTS["vol_p60_threshold"], -1000000000.0) def test_apply_pink_namespace_env_forces_testnet_namespace(self): sys.path.insert(0, str(Path(__file__).parent.parent)) from launch_dolphin_pink import _apply_pink_namespace_env with patch.dict( os.environ, { "DOLPHIN_BINGX_ENV": "LIVE", "DOLPHIN_BINGX_ALLOW_MAINNET": "1", "DOLPHIN_STATE_MAP": "DOLPHIN_STATE_PRODGREEN", "DOLPHIN_PNL_MAP": "DOLPHIN_PNL_PRODGREEN", "DOLPHIN_STRATEGY_NAME": "prodgreen", }, clear=False, ): _apply_pink_namespace_env() self.assertEqual(os.environ["DOLPHIN_BINGX_ENV"], "VST") self.assertEqual(os.environ["DOLPHIN_BINGX_ALLOW_MAINNET"], "0") self.assertEqual(os.environ["DOLPHIN_STRATEGY_NAME"], "pink") self.assertEqual(os.environ["DOLPHIN_STATE_MAP"], "DOLPHIN_STATE_PINK") self.assertEqual(os.environ["DOLPHIN_PNL_MAP"], "DOLPHIN_PNL_PINK") self.assertEqual(os.environ["DOLPHIN_FIXED_TP_PCT"], "0.0020") def test_apply_pink_actor_overrides_forces_alias_and_blue_sync_isolation(self): sys.path.insert(0, str(Path(__file__).parent.parent)) from launch_dolphin_pink import _apply_pink_actor_overrides actor_cfg = { "strategy_name": "prodgreen", "hazelcast": { "state_map": "DOLPHIN_STATE_PRODGREEN", "imap_pnl": "DOLPHIN_PNL_PRODGREEN", "state_map_aliases": ["DOLPHIN_STATE_GREEN"], "imap_pnl_aliases": ["DOLPHIN_PNL_GREEN"], }, "adaptive_exit": {"shadow_db": "dolphin_prodgreen"}, "v7_journal_db": "dolphin_prodgreen", "sync_bar_idx_from_blue": True, } updated = _apply_pink_actor_overrides(actor_cfg) self.assertEqual(updated["strategy_name"], "pink") self.assertEqual(updated["hazelcast"]["state_map"], "DOLPHIN_STATE_PINK") self.assertEqual(updated["hazelcast"]["imap_pnl"], "DOLPHIN_PNL_PINK") self.assertEqual(updated["hazelcast"]["state_map_aliases"], []) self.assertEqual(updated["hazelcast"]["imap_pnl_aliases"], []) self.assertEqual(updated["adaptive_exit"]["shadow_db"], "dolphin_pink") self.assertEqual(updated["v7_journal_db"], "dolphin_pink") self.assertEqual(updated["vol_p60_threshold"], -1000000000.0) self.assertEqual(updated["paper_trade"]["vol_p60"], -1000000000.0) self.assertFalse(updated["sync_bar_idx_from_blue"]) def test_apply_pink_actor_overrides_respects_env_vol_threshold(self): sys.path.insert(0, str(Path(__file__).parent.parent)) from launch_dolphin_pink import _apply_pink_actor_overrides with patch.dict(os.environ, {"DOLPHIN_PINK_VOL_P60_THRESHOLD": "0.00007000"}, clear=False): updated = _apply_pink_actor_overrides({"hazelcast": {}, "adaptive_exit": {}}) self.assertEqual(updated["vol_p60_threshold"], 0.00007000) class TestIsolationGuards(unittest.TestCase): """Verify PINK never aliases to BLUE namespaces.""" def test_pink_config_no_blue_maps(self): import yaml config_path = Path("/mnt/dolphinng5_predict/prod/configs/pink.yml") with open(config_path) as f: cfg = yaml.safe_load(f) state_map = cfg["hazelcast"]["state_map"] pnl_map = cfg["hazelcast"]["imap_pnl"] self.assertNotIn("BLUE", state_map) self.assertNotIn("BLUE", pnl_map) self.assertNotIn("PRODGREEN", state_map) self.assertNotIn("PRODGREEN", pnl_map) def test_pink_aliases_empty(self): import yaml config_path = Path("/mnt/dolphinng5_predict/prod/configs/pink.yml") with open(config_path) as f: cfg = yaml.safe_load(f) aliases = cfg["hazelcast"].get("state_map_aliases", []) pnl_aliases = cfg["hazelcast"].get("imap_pnl_aliases", []) self.assertEqual(aliases, []) self.assertEqual(pnl_aliases, []) class TestPinkClickHouseSchema(unittest.TestCase): """Test that PINK CH schema files exist.""" def test_schema_dir_exists(self): schema_dir = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink") self.assertTrue(schema_dir.is_dir()) def test_pink_schema_files_include_namespace_tags(self): schema_dir = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink") for file_name in [ "account_events.sql", "trade_events.sql", "v7_decision_events.sql", "adaptive_exit_shadow.sql", ]: text = (schema_dir / file_name).read_text() self.assertIn("runtime_namespace", text) self.assertIn("strategy_namespace", text) self.assertIn("event_namespace", text) self.assertIn("actor_name", text) self.assertIn("exec_venue", text) self.assertIn("data_venue", text) class TestPinkRowTagging(unittest.TestCase): """Test PINK writes carry standalone namespace tags.""" def test_dolphin_actor_tagged_ch_put_injects_pink_namespace(self): from nautilus_dolphin.nautilus.dolphin_actor import DolphinActor actor = DolphinActor.__new__(DolphinActor) actor._strategy_name = "pink" actor._pink_row_tags = { "runtime_namespace": "pink", "strategy_namespace": "pink", "event_namespace": "pink", "actor_name": "DolphinActor", "exec_venue": "BINGX", "data_venue": "BINANCE", } actor._ch_put_base = MagicMock() DolphinActor._pink_tagged_ch_put(actor, "trade_events", {"ts": 1, "strategy": "pink"}) actor._ch_put_base.assert_called_once() table, row = actor._ch_put_base.call_args.args self.assertEqual(table, "trade_events") self.assertEqual(row["strategy"], "pink") self.assertEqual(row["runtime_namespace"], "pink") self.assertEqual(row["strategy_namespace"], "pink") self.assertEqual(row["event_namespace"], "pink") self.assertEqual(row["actor_name"], "DolphinActor") self.assertEqual(row["exec_venue"], "BINGX") self.assertEqual(row["data_venue"], "BINANCE") def test_bingx_execution_client_tag_helper_returns_pink_tags(self): from prod.bingx.execution import BingxExecutionClient client = BingxExecutionClient.__new__(BingxExecutionClient) client._journal_strategy = "pink" tags = BingxExecutionClient._pink_observability_tags(client) self.assertEqual(tags["runtime_namespace"], "pink") self.assertEqual(tags["strategy_namespace"], "pink") self.assertEqual(tags["event_namespace"], "pink") self.assertEqual(tags["actor_name"], "BingxExecutionClient") self.assertEqual(tags["exec_venue"], "BINGX") self.assertEqual(tags["data_venue"], "BINGX") def test_adaptive_exit_engine_tag_helper_returns_pink_tags(self): from adaptive_exit.adaptive_exit_engine import AdaptiveExitEngine engine = object.__new__(AdaptiveExitEngine) engine._strategy_name = "pink" tags = AdaptiveExitEngine._row_tags(engine) self.assertEqual(tags["runtime_namespace"], "pink") self.assertEqual(tags["strategy_namespace"], "pink") self.assertEqual(tags["event_namespace"], "pink") self.assertEqual(tags["actor_name"], "AdaptiveExitEngine") self.assertEqual(tags["exec_venue"], "BINGX") self.assertEqual(tags["data_venue"], "BINGX") def test_required_schema_files(self): schema_dir = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink") required = [ "00_create_database.sql", "account_events.sql", "trade_events.sql", "status_snapshots.sql", "v7_decision_events.sql", "adaptive_exit_shadow.sql", "02_create_trade_reconstruction.sql", "03_create_trade_exit_legs.sql", ] for filename in required: self.assertTrue( (schema_dir / filename).exists(), f"Missing PINK schema file: {filename}", ) def test_schema_targets_dolphin_pink(self): schema_dir = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink") for sql_file in schema_dir.glob("*.sql"): content = sql_file.read_text() self.assertIn( "dolphin_pink", content, f"{sql_file.name} must reference dolphin_pink database", ) self.assertNotIn( "dolphin_prodgreen", content, f"{sql_file.name} must not reference dolphin_prodgreen", ) self.assertNotIn( "dolphin_green", content, f"{sql_file.name} must not reference dolphin_green", ) if __name__ == "__main__": unittest.main()