1ca1687c9b
DeribitSettings ora supporta coppie credenziali distinte per testnet e mainnet via DERIBIT_CLIENT_ID_TESTNET/_LIVE e DERIBIT_CLIENT_SECRET_TESTNET/_LIVE. Le coppie env-specifiche prevalgono sulla coppia base DERIBIT_CLIENT_ID/DERIBIT_CLIENT_SECRET (mantenuta per backward compat). build_client risolve la coppia giusta tramite settings.deribit.credentials(env); ValueError esplicito se nessuna coppia configurata per l'env richiesto. +4 test (legacy single, per-env, override, missing). Fix anche isolation da .env reale via monkeypatch.chdir(tmp_path). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
158 lines
5.2 KiB
Python
158 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
|
|
def _minimal_env(**overrides) -> dict:
|
|
base = {
|
|
"TESTNET_TOKEN": "t_test_123",
|
|
"MAINNET_TOKEN": "t_live_456",
|
|
"DERIBIT_CLIENT_ID": "id",
|
|
"DERIBIT_CLIENT_SECRET": "secret",
|
|
"DERIBIT_URL_LIVE": "https://www.deribit.com/api/v2",
|
|
"DERIBIT_URL_TESTNET": "https://test.deribit.com/api/v2",
|
|
"BYBIT_API_KEY": "k",
|
|
"BYBIT_API_SECRET": "s",
|
|
"BYBIT_URL_LIVE": "https://api.bybit.com",
|
|
"BYBIT_URL_TESTNET": "https://api-testnet.bybit.com",
|
|
"HYPERLIQUID_WALLET_ADDRESS": "0xabc",
|
|
"HYPERLIQUID_API_WALLET_ADDRESS": "0xdef",
|
|
"HYPERLIQUID_PRIVATE_KEY": "0x123",
|
|
"HYPERLIQUID_URL_LIVE": "https://api.hyperliquid.xyz",
|
|
"HYPERLIQUID_URL_TESTNET": "https://api.hyperliquid-testnet.xyz",
|
|
"ALPACA_API_KEY_ID": "k",
|
|
"ALPACA_SECRET_KEY": "s",
|
|
"ALPACA_URL_LIVE": "https://api.alpaca.markets",
|
|
"ALPACA_URL_TESTNET": "https://paper-api.alpaca.markets",
|
|
"FRED_API_KEY": "x",
|
|
"FINNHUB_API_KEY": "y",
|
|
"CRYPTOPANIC_KEY": "z",
|
|
"LUNARCRUSH_KEY": "w",
|
|
}
|
|
base.update(overrides)
|
|
return base
|
|
|
|
|
|
def test_settings_load_minimal(monkeypatch):
|
|
for k, v in _minimal_env().items():
|
|
monkeypatch.setenv(k, v)
|
|
monkeypatch.setenv("PORT", "9123")
|
|
|
|
from cerbero_mcp.settings import Settings
|
|
|
|
s = Settings()
|
|
assert s.port == 9123
|
|
assert s.host == "0.0.0.0"
|
|
assert s.testnet_token.get_secret_value() == "t_test_123"
|
|
assert s.mainnet_token.get_secret_value() == "t_live_456"
|
|
assert s.deribit.url_testnet.endswith("test.deribit.com/api/v2")
|
|
assert s.bybit.max_leverage == 3
|
|
assert s.alpaca.max_leverage == 1
|
|
|
|
|
|
def test_settings_missing_token_fails(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path) # isola dal .env reale del working dir
|
|
env = _minimal_env()
|
|
env.pop("TESTNET_TOKEN")
|
|
for k, v in env.items():
|
|
monkeypatch.setenv(k, v)
|
|
monkeypatch.delenv("TESTNET_TOKEN", raising=False)
|
|
|
|
from cerbero_mcp.settings import Settings
|
|
|
|
with pytest.raises(ValidationError):
|
|
Settings()
|
|
|
|
|
|
def test_settings_extras_ignored(monkeypatch):
|
|
for k, v in _minimal_env().items():
|
|
monkeypatch.setenv(k, v)
|
|
monkeypatch.setenv("UNRELATED_VAR", "ignored")
|
|
|
|
from cerbero_mcp.settings import Settings
|
|
|
|
s = Settings()
|
|
assert s.testnet_token.get_secret_value() == "t_test_123"
|
|
|
|
|
|
def test_settings_secret_str_no_leak(monkeypatch):
|
|
for k, v in _minimal_env().items():
|
|
monkeypatch.setenv(k, v)
|
|
|
|
from cerbero_mcp.settings import Settings
|
|
|
|
s = Settings()
|
|
assert "t_test_123" not in repr(s)
|
|
assert "t_live_456" not in repr(s)
|
|
|
|
|
|
def _isolated(monkeypatch, tmp_path, env: dict) -> None:
|
|
"""Isola Settings dal .env reale in working dir e setta solo env passato."""
|
|
monkeypatch.chdir(tmp_path)
|
|
for k in (
|
|
"DERIBIT_CLIENT_ID", "DERIBIT_CLIENT_SECRET",
|
|
"DERIBIT_CLIENT_ID_TESTNET", "DERIBIT_CLIENT_SECRET_TESTNET",
|
|
"DERIBIT_CLIENT_ID_LIVE", "DERIBIT_CLIENT_SECRET_LIVE",
|
|
):
|
|
monkeypatch.delenv(k, raising=False)
|
|
for k, v in env.items():
|
|
monkeypatch.setenv(k, v)
|
|
|
|
|
|
def test_deribit_credentials_legacy_single_pair(monkeypatch, tmp_path):
|
|
"""Solo DERIBIT_CLIENT_ID/SECRET → entrambi gli env usano la stessa coppia."""
|
|
_isolated(monkeypatch, tmp_path, _minimal_env())
|
|
|
|
from cerbero_mcp.settings import Settings
|
|
|
|
s = Settings()
|
|
assert s.deribit.credentials("testnet") == ("id", "secret")
|
|
assert s.deribit.credentials("mainnet") == ("id", "secret")
|
|
|
|
|
|
def test_deribit_credentials_per_env_pairs(monkeypatch, tmp_path):
|
|
"""Coppie _TESTNET e _LIVE → ognuna serve l'env corrispondente."""
|
|
env = _minimal_env()
|
|
env.pop("DERIBIT_CLIENT_ID")
|
|
env.pop("DERIBIT_CLIENT_SECRET")
|
|
env["DERIBIT_CLIENT_ID_TESTNET"] = "tid"
|
|
env["DERIBIT_CLIENT_SECRET_TESTNET"] = "tsec"
|
|
env["DERIBIT_CLIENT_ID_LIVE"] = "lid"
|
|
env["DERIBIT_CLIENT_SECRET_LIVE"] = "lsec"
|
|
_isolated(monkeypatch, tmp_path, env)
|
|
|
|
from cerbero_mcp.settings import Settings
|
|
|
|
s = Settings()
|
|
assert s.deribit.credentials("testnet") == ("tid", "tsec")
|
|
assert s.deribit.credentials("mainnet") == ("lid", "lsec")
|
|
|
|
|
|
def test_deribit_credentials_env_specific_overrides_fallback(monkeypatch, tmp_path):
|
|
"""_LIVE presente prevale sulla coppia base anche se entrambe configurate."""
|
|
env = _minimal_env()
|
|
env["DERIBIT_CLIENT_ID_LIVE"] = "lid"
|
|
env["DERIBIT_CLIENT_SECRET_LIVE"] = "lsec"
|
|
_isolated(monkeypatch, tmp_path, env)
|
|
|
|
from cerbero_mcp.settings import Settings
|
|
|
|
s = Settings()
|
|
assert s.deribit.credentials("mainnet") == ("lid", "lsec")
|
|
assert s.deribit.credentials("testnet") == ("id", "secret") # fallback
|
|
|
|
|
|
def test_deribit_credentials_missing_raises(monkeypatch, tmp_path):
|
|
"""Nessuna coppia configurata → ValueError esplicito."""
|
|
env = _minimal_env()
|
|
env.pop("DERIBIT_CLIENT_ID")
|
|
env.pop("DERIBIT_CLIENT_SECRET")
|
|
_isolated(monkeypatch, tmp_path, env)
|
|
|
|
from cerbero_mcp.settings import Settings
|
|
|
|
s = Settings()
|
|
with pytest.raises(ValueError, match="not configured for env=mainnet"):
|
|
s.deribit.credentials("mainnet")
|