""" Tests for Data Synchronization Service OrbiQuant IA Trading Platform """ import pytest from datetime import datetime, timedelta from unittest.mock import AsyncMock, MagicMock, patch from services.sync_service import DataSyncService, SyncStatus from providers.polygon_client import AssetType, Timeframe, OHLCVBar @pytest.fixture def mock_polygon_client(): """Mock Polygon client.""" client = MagicMock() client.get_ticker_details = AsyncMock(return_value={ "name": "EUR/USD", "primary_exchange": "FOREX" }) return client @pytest.fixture def mock_db_pool(): """Mock database pool.""" pool = MagicMock() # Mock connection conn = MagicMock() conn.fetchrow = AsyncMock(return_value={"id": 1, "last_ts": None}) conn.fetchval = AsyncMock(return_value=1) conn.fetch = AsyncMock(return_value=[]) conn.execute = AsyncMock() conn.executemany = AsyncMock() # Mock pool.acquire context manager pool.acquire = MagicMock() pool.acquire.return_value.__aenter__ = AsyncMock(return_value=conn) pool.acquire.return_value.__aexit__ = AsyncMock() return pool @pytest.fixture def sync_service(mock_polygon_client, mock_db_pool): """Create DataSyncService instance.""" return DataSyncService( polygon_client=mock_polygon_client, db_pool=mock_db_pool, batch_size=100 ) class TestDataSyncService: """Test DataSyncService class.""" @pytest.mark.asyncio async def test_get_or_create_ticker_existing(self, sync_service, mock_db_pool): """Test getting existing ticker.""" # Mock existing ticker conn = await mock_db_pool.acquire().__aenter__() conn.fetchrow.return_value = {"id": 123} ticker_id = await sync_service.get_or_create_ticker("EURUSD", AssetType.FOREX) assert ticker_id == 123 conn.fetchrow.assert_called_once() @pytest.mark.asyncio async def test_get_or_create_ticker_new(self, sync_service, mock_db_pool): """Test creating new ticker.""" # Mock no existing ticker, then return new ID conn = await mock_db_pool.acquire().__aenter__() conn.fetchrow.return_value = None conn.fetchval.return_value = 456 ticker_id = await sync_service.get_or_create_ticker("GBPUSD", AssetType.FOREX) assert ticker_id == 456 conn.fetchval.assert_called_once() @pytest.mark.asyncio async def test_sync_ticker_data_success(self, sync_service, mock_polygon_client): """Test successful ticker sync.""" # Mock data from Polygon async def mock_aggregates(*args, **kwargs): bars = [ OHLCVBar( timestamp=datetime.now(), open=1.1000, high=1.1050, low=1.0950, close=1.1025, volume=1000000, vwap=1.1012, transactions=1500 ) ] for bar in bars: yield bar mock_polygon_client.get_aggregates = mock_aggregates result = await sync_service.sync_ticker_data( symbol="EURUSD", asset_type=AssetType.FOREX, timeframe=Timeframe.MINUTE_5, backfill_days=1 ) assert result["status"] == SyncStatus.SUCCESS assert result["symbol"] == "EURUSD" assert result["rows_inserted"] >= 0 @pytest.mark.asyncio async def test_sync_ticker_data_no_ticker(self, sync_service, mock_db_pool): """Test sync when ticker creation fails.""" # Mock ticker creation failure conn = await mock_db_pool.acquire().__aenter__() conn.fetchrow.return_value = None conn.fetchval.return_value = None result = await sync_service.sync_ticker_data( symbol="INVALID", asset_type=AssetType.FOREX, backfill_days=1 ) assert result["status"] == SyncStatus.FAILED assert "Failed to get/create ticker" in result["error"] @pytest.mark.asyncio async def test_insert_bars(self, sync_service): """Test inserting bars.""" bars = [ (1, datetime.now(), 1.1, 1.15, 1.09, 1.12, 1000, 1.11, 100, 1234567890) ] inserted = await sync_service._insert_bars("ohlcv_5min", bars) assert inserted == 1 @pytest.mark.asyncio async def test_get_supported_symbols(self, sync_service): """Test getting supported symbols.""" symbols = await sync_service.get_supported_symbols() assert len(symbols) > 0 assert all("symbol" in s for s in symbols) assert all("asset_type" in s for s in symbols) @pytest.mark.asyncio async def test_get_supported_symbols_filtered(self, sync_service): """Test getting supported symbols with filter.""" forex_symbols = await sync_service.get_supported_symbols( asset_type=AssetType.FOREX ) assert len(forex_symbols) > 0 assert all(s["asset_type"] == "forex" for s in forex_symbols) @pytest.mark.asyncio async def test_get_sync_status(self, sync_service, mock_db_pool): """Test getting sync status.""" # Mock status data conn = await mock_db_pool.acquire().__aenter__() conn.fetch.return_value = [ { "symbol": "EURUSD", "asset_type": "forex", "timeframe": "5min", "last_sync_timestamp": datetime.now(), "last_sync_rows": 100, "sync_status": "success", "error_message": None, "updated_at": datetime.now() } ] status = await sync_service.get_sync_status() assert len(status) == 1 assert status[0]["symbol"] == "EURUSD" @pytest.mark.asyncio async def test_sync_all_active_tickers(self, sync_service, mock_db_pool, mock_polygon_client): """Test syncing all active tickers.""" # Mock active tickers conn = await mock_db_pool.acquire().__aenter__() conn.fetch.return_value = [ {"id": 1, "symbol": "EURUSD", "asset_type": "forex"}, {"id": 2, "symbol": "GBPUSD", "asset_type": "forex"} ] # Mock empty aggregates async def mock_aggregates(*args, **kwargs): return yield # Make it a generator mock_polygon_client.get_aggregates = mock_aggregates result = await sync_service.sync_all_active_tickers( timeframe=Timeframe.MINUTE_5, backfill_days=1 ) assert "total_tickers" in result assert "successful" in result assert "total_rows_inserted" in result class TestSyncStatus: """Test SyncStatus enum.""" def test_sync_status_values(self): """Test SyncStatus enum values.""" assert SyncStatus.PENDING == "pending" assert SyncStatus.IN_PROGRESS == "in_progress" assert SyncStatus.SUCCESS == "success" assert SyncStatus.FAILED == "failed" assert SyncStatus.PARTIAL == "partial" if __name__ == "__main__": pytest.main([__file__, "-v"])