"""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.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, ) httpx_mock.add_response( url="http://mcp-portfolio:9018/tools/get_total_portfolio_value", json={"total_value_eur": 1000.0}, is_reusable=True, ) def _build_orch(tmp_path: Path, *, expected: str = "testnet") -> 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"), ) @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, ) httpx_mock.add_response( url="http://mcp-telegram:9017/tools/notify_system_error", json={"ok": True}, 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"}