"""Tests for exchange helpers and caching.""" import pytest from coinhunter.services import exchange_service as es class TestNormSymbol: def test_already_normalized(self): assert es.norm_symbol("BTC/USDT") == "BTC/USDT" def test_raw_symbol(self): assert es.norm_symbol("BTCUSDT") == "BTC/USDT" def test_lowercase(self): assert es.norm_symbol("btcusdt") == "BTC/USDT" def test_dash_separator(self): assert es.norm_symbol("BTC-USDT") == "BTC/USDT" def test_underscore_separator(self): assert es.norm_symbol("BTC_USDT") == "BTC/USDT" def test_unsupported_symbol(self): with pytest.raises(ValueError): es.norm_symbol("BTC") class TestStorageSymbol: def test_converts_to_flat(self): assert es.storage_symbol("BTC/USDT") == "BTCUSDT" def test_raw_input(self): assert es.storage_symbol("ETHUSDT") == "ETHUSDT" class TestFloorToStep: def test_no_step(self): assert es.floor_to_step(1.234, 0) == 1.234 def test_floors_to_step(self): assert es.floor_to_step(1.234, 0.1) == pytest.approx(1.2) def test_exact_multiple(self): assert es.floor_to_step(12, 1) == 12 class TestGetExchangeCaching: def test_returns_cached_instance(self, monkeypatch): monkeypatch.setenv("BINANCE_API_KEY", "test_key") monkeypatch.setenv("BINANCE_API_SECRET", "test_secret") class FakeEx: def load_markets(self): pass # Reset cache and patch ccxt.binance original_cache = es._exchange_cache original_cached_at = es._exchange_cached_at try: es._exchange_cache = None es._exchange_cached_at = None monkeypatch.setattr(es.ccxt, "binance", lambda *a, **kw: FakeEx()) first = es.get_exchange() second = es.get_exchange() assert first is second third = es.get_exchange(force_new=True) assert third is not first finally: es._exchange_cache = original_cache es._exchange_cached_at = original_cached_at def test_cache_expires_after_ttl(self, monkeypatch): monkeypatch.setenv("BINANCE_API_KEY", "test_key") monkeypatch.setenv("BINANCE_API_SECRET", "test_secret") class FakeEx: def load_markets(self): pass original_cache = es._exchange_cache original_cached_at = es._exchange_cached_at try: es._exchange_cache = None es._exchange_cached_at = None monkeypatch.setattr(es.ccxt, "binance", lambda *a, **kw: FakeEx()) monkeypatch.setattr(es, "load_env", lambda: None) first = es.get_exchange() # Simulate time passing beyond TTL es._exchange_cached_at -= es.CACHE_TTL_SECONDS + 1 second = es.get_exchange() assert first is not second finally: es._exchange_cache = original_cache es._exchange_cached_at = original_cached_at