feat(mcp_common): add environment.resolve_environment with TDD
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
"""Resolver di ambiente (testnet/mainnet) per MCP exchange.
|
||||||
|
|
||||||
|
Precedenza: env var > campo secret > default True (testnet).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
Environment = Literal["testnet", "mainnet"]
|
||||||
|
Source = Literal["env", "credentials", "default"]
|
||||||
|
|
||||||
|
TRUTHY = {"1", "true", "yes", "on"}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class EnvironmentInfo:
|
||||||
|
exchange: str
|
||||||
|
environment: Environment
|
||||||
|
source: Source
|
||||||
|
env_value: str | None
|
||||||
|
base_url: str
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_environment(
|
||||||
|
creds: dict,
|
||||||
|
*,
|
||||||
|
env_var: str,
|
||||||
|
flag_key: str,
|
||||||
|
exchange: str,
|
||||||
|
) -> EnvironmentInfo:
|
||||||
|
"""Risolvi l'ambiente per un MCP exchange.
|
||||||
|
|
||||||
|
creds: dict letto dal secret JSON. Deve contenere base_url_live e base_url_testnet.
|
||||||
|
env_var: nome della env var di override (es. DERIBIT_TESTNET).
|
||||||
|
flag_key: chiave booleana nel secret JSON (es. "testnet" o "paper" per alpaca).
|
||||||
|
exchange: nome exchange per logging/info.
|
||||||
|
"""
|
||||||
|
env_value = os.environ.get(env_var)
|
||||||
|
if env_value is not None:
|
||||||
|
is_test = env_value.strip().lower() in TRUTHY
|
||||||
|
environment: Environment = "testnet" if is_test else "mainnet"
|
||||||
|
source: Source = "env"
|
||||||
|
elif flag_key in creds:
|
||||||
|
environment = "testnet" if bool(creds[flag_key]) else "mainnet"
|
||||||
|
source = "credentials"
|
||||||
|
else:
|
||||||
|
environment = "testnet"
|
||||||
|
source = "default"
|
||||||
|
|
||||||
|
base_url = creds["base_url_testnet"] if environment == "testnet" else creds["base_url_live"]
|
||||||
|
return EnvironmentInfo(
|
||||||
|
exchange=exchange,
|
||||||
|
environment=environment,
|
||||||
|
source=source,
|
||||||
|
env_value=env_value,
|
||||||
|
base_url=base_url,
|
||||||
|
)
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from mcp_common.environment import EnvironmentInfo, resolve_environment
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_var_overrides_secret(monkeypatch):
|
||||||
|
monkeypatch.setenv("DERIBIT_TESTNET", "false")
|
||||||
|
creds = {"testnet": True, "base_url_live": "L", "base_url_testnet": "T"}
|
||||||
|
info = resolve_environment(
|
||||||
|
creds,
|
||||||
|
env_var="DERIBIT_TESTNET",
|
||||||
|
flag_key="testnet",
|
||||||
|
exchange="deribit",
|
||||||
|
)
|
||||||
|
assert info.environment == "mainnet"
|
||||||
|
assert info.source == "env"
|
||||||
|
assert info.env_value == "false"
|
||||||
|
assert info.base_url == "L"
|
||||||
|
|
||||||
|
|
||||||
|
def test_secret_used_when_env_missing(monkeypatch):
|
||||||
|
monkeypatch.delenv("DERIBIT_TESTNET", raising=False)
|
||||||
|
creds = {"testnet": True, "base_url_live": "L", "base_url_testnet": "T"}
|
||||||
|
info = resolve_environment(
|
||||||
|
creds,
|
||||||
|
env_var="DERIBIT_TESTNET",
|
||||||
|
flag_key="testnet",
|
||||||
|
exchange="deribit",
|
||||||
|
)
|
||||||
|
assert info.environment == "testnet"
|
||||||
|
assert info.source == "credentials"
|
||||||
|
assert info.env_value is None
|
||||||
|
assert info.base_url == "T"
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_when_both_missing(monkeypatch):
|
||||||
|
monkeypatch.delenv("FOO_TESTNET", raising=False)
|
||||||
|
creds = {"base_url_live": "L", "base_url_testnet": "T"}
|
||||||
|
info = resolve_environment(
|
||||||
|
creds,
|
||||||
|
env_var="FOO_TESTNET",
|
||||||
|
flag_key="testnet",
|
||||||
|
exchange="foo",
|
||||||
|
)
|
||||||
|
assert info.environment == "testnet"
|
||||||
|
assert info.source == "default"
|
||||||
|
assert info.env_value is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("raw,expected", [
|
||||||
|
("1", "testnet"),
|
||||||
|
("true", "testnet"),
|
||||||
|
("yes", "testnet"),
|
||||||
|
("on", "testnet"),
|
||||||
|
("TRUE", "testnet"),
|
||||||
|
("0", "mainnet"),
|
||||||
|
("false", "mainnet"),
|
||||||
|
("no", "mainnet"),
|
||||||
|
("off", "mainnet"),
|
||||||
|
("garbage", "mainnet"),
|
||||||
|
])
|
||||||
|
def test_env_value_truthy_parsing(monkeypatch, raw, expected):
|
||||||
|
monkeypatch.setenv("X_TESTNET", raw)
|
||||||
|
info = resolve_environment(
|
||||||
|
{"base_url_live": "L", "base_url_testnet": "T"},
|
||||||
|
env_var="X_TESTNET",
|
||||||
|
flag_key="testnet",
|
||||||
|
exchange="x",
|
||||||
|
)
|
||||||
|
assert info.environment == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_alpaca_paper_flag_key(monkeypatch):
|
||||||
|
"""Alpaca usa 'paper' invece di 'testnet' nel secret."""
|
||||||
|
monkeypatch.delenv("ALPACA_PAPER", raising=False)
|
||||||
|
creds = {"paper": False, "base_url_live": "L", "base_url_testnet": "T"}
|
||||||
|
info = resolve_environment(
|
||||||
|
creds,
|
||||||
|
env_var="ALPACA_PAPER",
|
||||||
|
flag_key="paper",
|
||||||
|
exchange="alpaca",
|
||||||
|
)
|
||||||
|
assert info.environment == "mainnet"
|
||||||
|
assert info.source == "credentials"
|
||||||
Reference in New Issue
Block a user