abf5a140e2
Each bot now manages its own notification + portfolio aggregation: * TelegramClient calls the public Bot API directly via httpx, reading CERBERO_BITE_TELEGRAM_BOT_TOKEN / CERBERO_BITE_TELEGRAM_CHAT_ID from env. No credentials → silent disabled mode. * PortfolioClient composes DeribitClient + HyperliquidClient + the new MacroClient.get_asset_price/eur_usd_rate to expose equity (EUR) and per-asset exposure as the bot's own slice (no cross-bot view). * mcp-telegram and mcp-portfolio removed from MCP_SERVICES / McpEndpoints and the cerbero-bite ping CLI; health_check no longer probes portfolio. Docs (02/04/06/07) and docker-compose updated to reflect the new architecture. 353/353 tests pass; ruff clean; mypy src clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
"""End-to-end test for ``cerbero-bite ping``.
|
|
|
|
The CLI uses the production code paths, so we set up an HTTP mock that
|
|
matches every URL Bite is going to hit and assert the rendered output
|
|
contains the expected statuses.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from click.testing import CliRunner
|
|
from pytest_httpx import HTTPXMock
|
|
|
|
from cerbero_bite.cli import main as cli_main
|
|
|
|
|
|
def _seed_token(tmp_path: Path) -> Path:
|
|
target = tmp_path / "core_token"
|
|
target.write_text("super-secret\n", encoding="utf-8")
|
|
return target
|
|
|
|
|
|
def test_ping_reports_each_service(
|
|
tmp_path: Path, httpx_mock: HTTPXMock
|
|
) -> None:
|
|
token_file = _seed_token(tmp_path)
|
|
|
|
httpx_mock.add_response(
|
|
url="http://mcp-deribit:9011/tools/environment_info",
|
|
json={
|
|
"exchange": "deribit",
|
|
"environment": "testnet",
|
|
"source": "env",
|
|
"env_value": "true",
|
|
"base_url": "https://test.deribit.com/api/v2",
|
|
"max_leverage": 3,
|
|
},
|
|
)
|
|
httpx_mock.add_response(
|
|
url="http://mcp-hyperliquid:9012/tools/get_funding_rate",
|
|
json={"asset": "ETH", "current_funding_rate": 0.0001},
|
|
)
|
|
httpx_mock.add_response(
|
|
url="http://mcp-macro:9013/tools/get_macro_calendar",
|
|
json={"events": []},
|
|
)
|
|
httpx_mock.add_response(
|
|
url="http://mcp-sentiment:9014/tools/get_cross_exchange_funding",
|
|
json={"snapshot": {"ETH": {"binance": 0.0001}}},
|
|
)
|
|
|
|
result = CliRunner().invoke(
|
|
cli_main, ["ping", "--token-file", str(token_file), "--timeout", "1.0"]
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
assert "deribit" in result.output
|
|
assert "hyperliquid" in result.output
|
|
assert "macro" in result.output
|
|
assert "sentiment" in result.output
|
|
# Telegram and Portfolio are no longer MCP services and are not
|
|
# listed by the ping command.
|
|
assert "portfolio" not in result.output
|
|
assert "OK" in result.output
|
|
|
|
|
|
def test_ping_reports_failure_when_service_unreachable(
|
|
tmp_path: Path, httpx_mock: HTTPXMock
|
|
) -> None:
|
|
token_file = _seed_token(tmp_path)
|
|
httpx_mock.add_response(
|
|
url="http://mcp-deribit:9011/tools/environment_info",
|
|
status_code=500,
|
|
text="boom",
|
|
)
|
|
# Provide successful stubs for the others to keep the call small.
|
|
httpx_mock.add_response(
|
|
url="http://mcp-hyperliquid:9012/tools/get_funding_rate",
|
|
json={"asset": "ETH", "current_funding_rate": 0.0001},
|
|
)
|
|
httpx_mock.add_response(
|
|
url="http://mcp-macro:9013/tools/get_macro_calendar",
|
|
json={"events": []},
|
|
)
|
|
httpx_mock.add_response(
|
|
url="http://mcp-sentiment:9014/tools/get_cross_exchange_funding",
|
|
json={"snapshot": {"ETH": {"binance": 0.0001}}},
|
|
)
|
|
|
|
result = CliRunner().invoke(
|
|
cli_main, ["ping", "--token-file", str(token_file), "--timeout", "1.0"]
|
|
)
|
|
assert result.exit_code == 0
|
|
assert "FAIL" in result.output
|
|
|
|
|
|
def test_ping_token_missing_exits_nonzero(tmp_path: Path) -> None:
|
|
result = CliRunner().invoke(
|
|
cli_main, ["ping", "--token-file", str(tmp_path / "nope")]
|
|
)
|
|
assert result.exit_code == 1
|
|
assert "token error" in result.output
|