diff --git a/tests/test_exchange.py b/tests/test_exchange.py new file mode 100644 index 0000000..d4c8808 --- /dev/null +++ b/tests/test_exchange.py @@ -0,0 +1,81 @@ +"""Tests for the main mt5bridge exchange class.""" + +import responses +import pytest + +import mt5bridge_ccxt +from mt5bridge_ccxt.exceptions import ( + Mt5BridgeAuthError, + Mt5BridgeConnectionError, + Mt5BridgeNotConnectedError, + Mt5BridgeInvalidRequestError, +) + + +class TestMt5bridgeInit: + def test_id_and_name(self, exchange_config): + ex = mt5bridge_ccxt.mt5bridge(exchange_config) + assert ex.id == "mt5bridge" + assert ex.name == "MT5 Bridge" + assert ex.apiKey == exchange_config["apiKey"] + assert ex.host == "http://mock-mt5bridge:8080" + + def test_default_host(self): + ex = mt5bridge_ccxt.mt5bridge({"apiKey": "x"}) + assert ex.host == "http://localhost:8080" + + def test_timeframes(self, exchange_config): + ex = mt5bridge_ccxt.mt5bridge(exchange_config) + assert "1m" in ex.timeframes + assert "1h" in ex.timeframes + assert "1d" in ex.timeframes + assert ex.timeframes["1h"] == "TIMEFRAME_H1" + + def test_mql5_bridge_attached(self, exchange_config): + ex = mt5bridge_ccxt.mt5bridge(exchange_config) + assert ex.mql5 is not None + assert hasattr(ex.mql5, "alpha_trend_signal") + + def test_strip_slash_fallback(self): + ex = mt5bridge_ccxt.mt5bridge({ + "apiKey": "x", + "host": "http://h:8080", + }) + # Without symbol_map, falls back to stripping slash + assert ex._resolve_mt5_symbol("XAU/USD") == "XAUUSD" + assert ex._resolve_mt5_symbol("BTC/USDT") == "BTCUSDT" + + def test_symbol_map_used(self, exchange_config): + ex = mt5bridge_ccxt.mt5bridge(exchange_config) + assert ex._resolve_mt5_symbol("XAU/USD") == "XAUUSDc" + assert ex._resolve_mt5_symbol("EUR/USD") == "EURUSDc" + + def test_reverse_symbol_map(self, exchange_config): + ex = mt5bridge_ccxt.mt5bridge(exchange_config) + assert ex._resolve_ccxt_symbol("XAUUSDc") == "XAU/USD" + + +class TestErrorHandling: + @responses.activate + def test_401_raises_auth_error(self, exchange_config): + responses.add(responses.GET, "http://mock-mt5bridge:8080/account", + json={"detail": "Unauthorized"}, status=401) + ex = mt5bridge_ccxt.mt5bridge(exchange_config) + with pytest.raises(Mt5BridgeAuthError): + ex.fetch_balance() + + @responses.activate + def test_503_raises_not_connected(self, exchange_config): + responses.add(responses.GET, "http://mock-mt5bridge:8080/account", + json={"detail": "MT5 not connected"}, status=503) + ex = mt5bridge_ccxt.mt5bridge(exchange_config) + with pytest.raises(Mt5BridgeNotConnectedError): + ex.fetch_balance() + + @responses.activate + def test_400_raises_invalid_request(self, exchange_config): + responses.add(responses.GET, "http://mock-mt5bridge:8080/account", + json={"detail": "Bad symbol"}, status=400) + ex = mt5bridge_ccxt.mt5bridge(exchange_config) + with pytest.raises(Mt5BridgeInvalidRequestError): + ex.fetch_balance()