feat(safety+audit+deploy): consistency_check + audit log file sink + deploy script
#2 Env switch safety: - mcp_common/environment.py: nuova consistency_check() che previene switch accidentali a mainnet. Solleva EnvironmentMismatchError se resolved=mainnet senza creds["environment"]="mainnet" esplicito, o se declared/resolved mismatch. Override via STRICT_MAINNET=false. - Wirato in app_factory.run_exchange_main al boot. - 6 nuovi test consistency. #3 Audit log persistence: - mcp_common/audit.py: TimedRotatingFileHandler aggiuntivo se env AUDIT_LOG_FILE settato. Rotation midnight UTC, retention 30gg default (AUDIT_LOG_BACKUP_DAYS). Format JSONL con SecretsFilter. - docker-compose.prod.yml: bind mount /var/log/cerbero-mcp + env AUDIT_LOG_FILE per i 4 servizi exchange (write endpoints). - 2 nuovi test file sink. #1 Deploy script: - scripts/deploy.sh: idempotente, fa docker login + clone/pull repo + copia secrets chmod 600 + crea .env + setup audit dir + pull image + up + smoke test pubblico HTTPS. - DEPLOYMENT.md aggiornato: sezioni 2 (script), 3 (safety mainnet), 4 (audit log query), renumber sezioni successive. Test: 488/488 verdi. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from mcp_common.environment import resolve_environment
|
||||
from mcp_common.environment import (
|
||||
EnvironmentMismatchError,
|
||||
consistency_check,
|
||||
resolve_environment,
|
||||
)
|
||||
|
||||
|
||||
def test_env_var_overrides_secret(monkeypatch):
|
||||
@@ -114,3 +118,72 @@ def test_alpaca_paper_flag_key(monkeypatch):
|
||||
)
|
||||
assert info.environment == "mainnet"
|
||||
assert info.source == "credentials"
|
||||
|
||||
|
||||
# ───────── consistency_check ─────────
|
||||
|
||||
|
||||
def _info(env: str, exchange: str = "deribit") -> "EnvironmentInfo":
|
||||
"""Helper costruisce EnvironmentInfo per test."""
|
||||
from mcp_common.environment import EnvironmentInfo
|
||||
return EnvironmentInfo(
|
||||
exchange=exchange,
|
||||
environment=env,
|
||||
source="env",
|
||||
env_value="false" if env == "mainnet" else "true",
|
||||
base_url=f"https://api.{exchange}.com" if env == "mainnet" else f"https://test.{exchange}.com",
|
||||
)
|
||||
|
||||
|
||||
def test_consistency_check_testnet_no_confirmation_ok():
|
||||
"""Testnet senza conferma esplicita → ok, ritorna []. Default safe."""
|
||||
info = _info("testnet")
|
||||
creds = {"api_key": "k", "api_secret": "s"}
|
||||
warnings = consistency_check(info, creds)
|
||||
assert warnings == []
|
||||
|
||||
|
||||
def test_consistency_check_mainnet_no_confirmation_raises():
|
||||
"""Mainnet senza creds['environment']='mainnet' esplicito → fail-fast."""
|
||||
info = _info("mainnet")
|
||||
creds = {"api_key": "k", "api_secret": "s"}
|
||||
with pytest.raises(EnvironmentMismatchError, match="mainnet.*explicit confirmation"):
|
||||
consistency_check(info, creds)
|
||||
|
||||
|
||||
def test_consistency_check_mainnet_with_confirmation_ok():
|
||||
info = _info("mainnet")
|
||||
creds = {"api_key": "k", "api_secret": "s", "environment": "mainnet"}
|
||||
warnings = consistency_check(info, creds)
|
||||
assert warnings == []
|
||||
|
||||
|
||||
def test_consistency_check_explicit_mismatch_raises():
|
||||
"""Secret dichiara mainnet ma resolver risolve testnet → fail-fast."""
|
||||
info = _info("testnet")
|
||||
creds = {"environment": "mainnet"}
|
||||
with pytest.raises(EnvironmentMismatchError, match="declared.*resolved"):
|
||||
consistency_check(info, creds)
|
||||
|
||||
|
||||
def test_consistency_check_strict_mainnet_disabled():
|
||||
"""Con strict_mainnet=False mainnet senza conferma logga warning ma non raise."""
|
||||
info = _info("mainnet")
|
||||
creds = {"api_key": "k", "api_secret": "s"}
|
||||
warnings = consistency_check(info, creds, strict_mainnet=False)
|
||||
assert any("mainnet" in w for w in warnings)
|
||||
|
||||
|
||||
def test_consistency_check_url_does_not_match_environment_warns():
|
||||
"""Base URL contiene 'test' ma environment='mainnet' → warning."""
|
||||
from mcp_common.environment import EnvironmentInfo
|
||||
info = EnvironmentInfo(
|
||||
exchange="bybit",
|
||||
environment="mainnet",
|
||||
source="env",
|
||||
env_value="false",
|
||||
base_url="https://api-testnet.bybit.com", # url DICE testnet ma resolver MAINNET
|
||||
)
|
||||
creds = {"environment": "mainnet"}
|
||||
warnings = consistency_check(info, creds)
|
||||
assert any("base_url" in w.lower() for w in warnings)
|
||||
|
||||
Reference in New Issue
Block a user