from __future__ import annotations import json from unittest.mock import MagicMock, patch import pytest from mcp_common.app_factory import ExchangeAppSpec, run_exchange_main from mcp_common.environment import EnvironmentInfo def _make_spec(build_client=None, build_app=None) -> ExchangeAppSpec: return ExchangeAppSpec( exchange="testex", creds_env_var="TESTEX_CREDENTIALS_FILE", env_var="TESTEX_TESTNET", flag_key="testnet", default_base_url_live="https://api.testex.com", default_base_url_testnet="https://test.testex.com", default_port=9999, build_client=build_client or (lambda creds, env_info: MagicMock(name="client")), build_app=build_app or (lambda **kwargs: MagicMock(name="app")), ) def test_run_exchange_main_loads_creds_and_resolves_env(tmp_path, monkeypatch): creds_file = tmp_path / "creds.json" creds_file.write_text(json.dumps({"api_key": "k", "api_secret": "s"})) monkeypatch.setenv("TESTEX_CREDENTIALS_FILE", str(creds_file)) monkeypatch.setenv("PORT", "10000") monkeypatch.delenv("TESTEX_TESTNET", raising=False) captured: dict = {} def build_client(creds, env_info): captured["creds"] = creds captured["env_info"] = env_info return MagicMock() def build_app(**kwargs): captured["app_kwargs"] = kwargs return MagicMock() spec = _make_spec(build_client=build_client, build_app=build_app) with patch("mcp_common.app_factory.uvicorn.run") as mock_run: run_exchange_main(spec) assert captured["creds"]["api_key"] == "k" assert captured["creds"]["base_url_live"] == "https://api.testex.com" assert captured["creds"]["base_url_testnet"] == "https://test.testex.com" assert isinstance(captured["env_info"], EnvironmentInfo) assert captured["env_info"].environment == "testnet" assert captured["env_info"].exchange == "testex" assert "client" in captured["app_kwargs"] assert "token_store" in captured["app_kwargs"] assert "creds" in captured["app_kwargs"] assert "env_info" in captured["app_kwargs"] call_kwargs = mock_run.call_args.kwargs assert call_kwargs["port"] == 10000 # PORT override def test_run_exchange_main_uses_default_port(tmp_path, monkeypatch): creds_file = tmp_path / "creds.json" creds_file.write_text(json.dumps({})) monkeypatch.setenv("TESTEX_CREDENTIALS_FILE", str(creds_file)) monkeypatch.delenv("PORT", raising=False) spec = _make_spec() with patch("mcp_common.app_factory.uvicorn.run") as mock_run: run_exchange_main(spec) assert mock_run.call_args.kwargs["port"] == 9999 def test_run_exchange_main_env_var_overrides_creds(tmp_path, monkeypatch): creds_file = tmp_path / "creds.json" # `environment: mainnet` esplicito perché env var override → mainnet # e consistency_check richiede conferma per evitare switch accidentale. creds_file.write_text(json.dumps({"testnet": True, "environment": "mainnet"})) monkeypatch.setenv("TESTEX_CREDENTIALS_FILE", str(creds_file)) monkeypatch.setenv("TESTEX_TESTNET", "false") captured: dict = {} def build_client(creds, env_info): captured["env_info"] = env_info return MagicMock() spec = _make_spec(build_client=build_client) with patch("mcp_common.app_factory.uvicorn.run"): run_exchange_main(spec) # env var "false" overrides creds.testnet=True → mainnet assert captured["env_info"].environment == "mainnet" assert captured["env_info"].source == "env" def test_run_exchange_main_aborts_on_mainnet_without_confirmation(tmp_path, monkeypatch): """Mainnet senza creds['environment']='mainnet' → boot abort fail-fast.""" from mcp_common.environment import EnvironmentMismatchError creds_file = tmp_path / "creds.json" creds_file.write_text(json.dumps({"testnet": False})) monkeypatch.setenv("TESTEX_CREDENTIALS_FILE", str(creds_file)) monkeypatch.delenv("TESTEX_TESTNET", raising=False) monkeypatch.delenv("STRICT_MAINNET", raising=False) spec = _make_spec() with pytest.raises(EnvironmentMismatchError): with patch("mcp_common.app_factory.uvicorn.run"): run_exchange_main(spec) def test_run_exchange_main_strict_mainnet_disabled_via_env(tmp_path, monkeypatch): """STRICT_MAINNET=false permette mainnet senza conferma (warning soltanto).""" creds_file = tmp_path / "creds.json" creds_file.write_text(json.dumps({"testnet": False})) monkeypatch.setenv("TESTEX_CREDENTIALS_FILE", str(creds_file)) monkeypatch.setenv("STRICT_MAINNET", "false") spec = _make_spec() with patch("mcp_common.app_factory.uvicorn.run"): run_exchange_main(spec) # non solleva def test_run_exchange_main_missing_creds_file_exits(monkeypatch): monkeypatch.delenv("TESTEX_CREDENTIALS_FILE", raising=False) spec = _make_spec() import pytest with pytest.raises(SystemExit) as exc_info: run_exchange_main(spec) assert exc_info.value.code == 2