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")