"""Integration tests for the Orchestrator façade (boot + cycle wiring).""" from __future__ import annotations from datetime import UTC, datetime from decimal import Decimal from pathlib import Path import pytest from pytest_httpx import HTTPXMock from cerbero_bite.config import golden_config from cerbero_bite.config.mcp_endpoints import load_endpoints from cerbero_bite.config.runtime_flags import RuntimeFlags from cerbero_bite.runtime import Orchestrator from cerbero_bite.runtime.dependencies import build_runtime pytestmark = pytest.mark.httpx_mock(assert_all_responses_were_requested=False) def _now() -> datetime: return datetime(2026, 4, 27, 14, 0, tzinfo=UTC) def _wire_environment_info( httpx_mock: HTTPXMock, *, environment: str = "testnet", ) -> None: httpx_mock.add_response( url="http://mcp-deribit:9011/tools/environment_info", json={ "exchange": "deribit", "environment": environment, "source": "env", "env_value": "true" if environment == "testnet" else "false", "base_url": "https://test.deribit.com/api/v2", "max_leverage": 3, }, is_reusable=True, ) def _wire_health_probes(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( url="http://mcp-macro:9013/tools/get_macro_calendar", json={"events": []}, is_reusable=True, ) httpx_mock.add_response( url="http://mcp-sentiment:9014/tools/get_cross_exchange_funding", json={"snapshot": {}}, is_reusable=True, ) httpx_mock.add_response( url="http://mcp-hyperliquid:9012/tools/get_funding_rate", json={"asset": "ETH", "current_funding_rate": 0.0001}, is_reusable=True, ) def _build_orch( tmp_path: Path, *, expected: str = "testnet", flags: RuntimeFlags | None = None, ) -> Orchestrator: ctx = build_runtime( cfg=golden_config(), endpoints=load_endpoints(env={}), token="t", db_path=tmp_path / "state.sqlite", audit_path=tmp_path / "audit.log", retry_max=1, clock=_now, ) return Orchestrator( ctx, expected_environment=expected, # type: ignore[arg-type] eur_to_usd=Decimal("1.075"), flags=flags or RuntimeFlags(data_analysis_enabled=True, strategy_enabled=True), ) @pytest.mark.asyncio async def test_boot_succeeds_when_environment_matches( tmp_path: Path, httpx_mock: HTTPXMock ) -> None: _wire_environment_info(httpx_mock, environment="testnet") _wire_health_probes(httpx_mock) httpx_mock.add_response( url="http://mcp-deribit:9011/tools/get_positions", json=[], is_reusable=True, ) orch = _build_orch(tmp_path, expected="testnet") boot = await orch.boot() assert boot.environment == "testnet" assert boot.health.state == "ok" assert orch.context.kill_switch.is_armed() is False @pytest.mark.asyncio async def test_boot_arms_kill_switch_on_environment_mismatch( tmp_path: Path, httpx_mock: HTTPXMock ) -> None: _wire_environment_info(httpx_mock, environment="mainnet") _wire_health_probes(httpx_mock) httpx_mock.add_response( url="http://mcp-deribit:9011/tools/get_positions", json=[], is_reusable=True, ) orch = _build_orch(tmp_path, expected="testnet") await orch.boot() assert orch.context.kill_switch.is_armed() is True def test_install_scheduler_registers_canonical_jobs(tmp_path: Path) -> None: orch = _build_orch(tmp_path) sched = orch.install_scheduler() job_ids = {j.id for j in sched.get_jobs()} assert job_ids == { "entry", "monitor", "health", "backup", "manual_actions", "market_snapshot", } def test_install_scheduler_skips_strategy_jobs_when_disabled(tmp_path: Path) -> None: orch = _build_orch( tmp_path, flags=RuntimeFlags(data_analysis_enabled=True, strategy_enabled=False), ) sched = orch.install_scheduler() job_ids = {j.id for j in sched.get_jobs()} assert "entry" not in job_ids assert "monitor" not in job_ids # data analysis stays on, plus the always-on infra jobs. assert {"health", "backup", "manual_actions", "market_snapshot"}.issubset(job_ids) def test_install_scheduler_skips_market_snapshot_when_data_analysis_off( tmp_path: Path, ) -> None: orch = _build_orch( tmp_path, flags=RuntimeFlags(data_analysis_enabled=False, strategy_enabled=True), ) sched = orch.install_scheduler() job_ids = {j.id for j in sched.get_jobs()} assert "market_snapshot" not in job_ids assert {"entry", "monitor", "health", "backup", "manual_actions"}.issubset( job_ids ) def test_install_scheduler_analysis_only_default(tmp_path: Path) -> None: """The default RuntimeFlags profile (analysis only) drops entry/monitor.""" orch = _build_orch(tmp_path, flags=RuntimeFlags()) sched = orch.install_scheduler() job_ids = {j.id for j in sched.get_jobs()} assert "entry" not in job_ids assert "monitor" not in job_ids assert "market_snapshot" in job_ids