"""Tests for api_moneyflow module. Tests the moneyflow (个股资金流向) API wrapper. """ import pytest import pandas as pd from unittest.mock import patch, MagicMock from src.data.api_wrappers.api_moneyflow import ( get_moneyflow, sync_moneyflow, preview_moneyflow_sync, MoneyflowSync, ) class TestMoneyflowAPI: """Test suite for moneyflow API wrapper.""" @patch("src.data.api_wrappers.api_moneyflow.TushareClient") def test_get_moneyflow_by_date(self, mock_client_class): """Test fetching moneyflow data by date.""" # Setup mock mock_client = MagicMock() mock_client_class.return_value = mock_client mock_client.query.return_value = pd.DataFrame( { "ts_code": ["000001.SZ", "000002.SZ"], "trade_date": ["20240115", "20240115"], "buy_sm_vol": [10000, 20000], "buy_sm_amount": [100.5, 200.5], "sell_sm_vol": [8000, 15000], "sell_sm_amount": [80.5, 150.5], "buy_md_vol": [5000, 10000], "buy_md_amount": [500.5, 1000.5], "sell_md_vol": [4000, 8000], "sell_md_amount": [400.5, 800.5], "buy_lg_vol": [2000, 5000], "buy_lg_amount": [2000.5, 5000.5], "sell_lg_vol": [1500, 4000], "sell_lg_amount": [1500.5, 4000.5], "buy_elg_vol": [1000, 3000], "buy_elg_amount": [5000.5, 15000.5], "sell_elg_vol": [800, 2500], "sell_elg_amount": [4000.5, 12500.5], "net_mf_vol": [2700, 8000], "net_mf_amount": [220.0, 550.0], } ) # Test result = get_moneyflow(trade_date="20240115") # Assert assert not result.empty assert len(result) == 2 assert "ts_code" in result.columns assert "trade_date" in result.columns assert "buy_sm_vol" in result.columns assert "net_mf_amount" in result.columns mock_client.query.assert_called_once_with("moneyflow", trade_date="20240115") @patch("src.data.api_wrappers.api_moneyflow.TushareClient") def test_get_moneyflow_by_stock(self, mock_client_class): """Test fetching moneyflow data by stock code.""" # Setup mock mock_client = MagicMock() mock_client_class.return_value = mock_client mock_client.query.return_value = pd.DataFrame( { "ts_code": ["000001.SZ", "000001.SZ"], "trade_date": ["20240114", "20240115"], "buy_sm_vol": [10000, 11000], "buy_sm_amount": [100.5, 110.5], "sell_sm_vol": [8000, 8500], "sell_sm_amount": [80.5, 85.5], "buy_md_vol": [5000, 5500], "buy_md_amount": [500.5, 550.5], "sell_md_vol": [4000, 4200], "sell_md_amount": [400.5, 420.5], "buy_lg_vol": [2000, 2200], "buy_lg_amount": [2000.5, 2200.5], "sell_lg_vol": [1500, 1600], "sell_lg_amount": [1500.5, 1600.5], "buy_elg_vol": [1000, 1100], "buy_elg_amount": [5000.5, 5500.5], "sell_elg_vol": [800, 850], "sell_elg_amount": [4000.5, 4250.5], "net_mf_vol": [2700, 2950], "net_mf_amount": [220.0, 245.0], } ) # Test result = get_moneyflow( ts_code="000001.SZ", start_date="20240101", end_date="20240131" ) # Assert assert not result.empty assert len(result) == 2 mock_client.query.assert_called_once_with( "moneyflow", ts_code="000001.SZ", start_date="20240101", end_date="20240131" ) @patch("src.data.api_wrappers.api_moneyflow.TushareClient") def test_get_moneyflow_empty_response(self, mock_client_class): """Test handling empty response.""" # Setup mock mock_client = MagicMock() mock_client_class.return_value = mock_client mock_client.query.return_value = pd.DataFrame() # Test result = get_moneyflow(trade_date="20240101") # Assert assert result.empty @patch("src.data.api_wrappers.api_moneyflow.TushareClient") def test_get_moneyflow_with_shared_client(self, mock_client_class): """Test fetching with shared client for rate limiting.""" # Setup mock shared client mock_shared_client = MagicMock() mock_shared_client.query.return_value = pd.DataFrame( { "ts_code": ["000001.SZ"], "trade_date": ["20240115"], "buy_sm_vol": [10000], "net_mf_amount": [220.0], } ) # Test with shared client result = get_moneyflow(trade_date="20240115", client=mock_shared_client) # Assert assert not result.empty mock_shared_client.query.assert_called_once_with( "moneyflow", trade_date="20240115" ) mock_client_class.assert_not_called() # Should not create new client @patch("src.data.api_wrappers.api_moneyflow.TushareClient") def test_get_moneyflow_date_column_rename(self, mock_client_class): """Test that 'date' column is renamed to 'trade_date'.""" # Setup mock with 'date' column (should be renamed) mock_client = MagicMock() mock_client_class.return_value = mock_client mock_client.query.return_value = pd.DataFrame( { "ts_code": ["000001.SZ"], "date": ["20240115"], # Using 'date' instead of 'trade_date' "buy_sm_vol": [10000], "net_mf_amount": [220.0], } ) # Test result = get_moneyflow(trade_date="20240115") # Assert - date column should be renamed to trade_date assert "trade_date" in result.columns assert "date" not in result.columns assert result["trade_date"].iloc[0] == "20240115" class TestMoneyflowSync: """Test suite for MoneyflowSync class.""" def test_moneyflow_sync_class_attributes(self): """Test MoneyflowSync class has correct attributes.""" assert MoneyflowSync.table_name == "moneyflow" assert MoneyflowSync.default_start_date == "20100101" assert "ts_code" in MoneyflowSync.TABLE_SCHEMA assert "trade_date" in MoneyflowSync.TABLE_SCHEMA assert "buy_sm_vol" in MoneyflowSync.TABLE_SCHEMA assert "net_mf_amount" in MoneyflowSync.TABLE_SCHEMA assert MoneyflowSync.PRIMARY_KEY == ("ts_code", "trade_date") @patch("src.data.api_wrappers.api_moneyflow.get_moneyflow") @patch("src.data.api_wrappers.api_moneyflow.TushareClient") def test_fetch_single_date(self, mock_client_class, mock_get_moneyflow): """Test fetch_single_date method.""" # Setup mock mock_get_moneyflow.return_value = pd.DataFrame( { "ts_code": ["000001.SZ"], "trade_date": ["20240115"], "buy_sm_vol": [10000], "net_mf_amount": [220.0], } ) # Create sync instance sync = MoneyflowSync() # Test result = sync.fetch_single_date("20240115") # Assert assert not result.empty mock_get_moneyflow.assert_called_once_with( trade_date="20240115", client=sync.client ) @patch("src.data.api_wrappers.api_moneyflow.MoneyflowSync.sync_all") def test_sync_moneyflow_function(self, mock_sync_all): """Test sync_moneyflow convenience function.""" # Setup mock mock_sync_all.return_value = pd.DataFrame( { "ts_code": ["000001.SZ"], "trade_date": ["20240115"], } ) # Test result = sync_moneyflow(start_date="20240101", end_date="20240131") # Assert assert not result.empty mock_sync_all.assert_called_once_with( start_date="20240101", end_date="20240131", force_full=False ) @patch("src.data.api_wrappers.api_moneyflow.MoneyflowSync.preview_sync") def test_preview_moneyflow_sync_function(self, mock_preview_sync): """Test preview_moneyflow_sync convenience function.""" # Setup mock mock_preview_sync.return_value = { "sync_needed": True, "date_count": 31, "start_date": "20240101", "end_date": "20240131", "estimated_records": 10000, "sample_data": pd.DataFrame(), "mode": "incremental", } # Test result = preview_moneyflow_sync(start_date="20240101", end_date="20240131") # Assert assert result["sync_needed"] is True assert result["date_count"] == 31 mock_preview_sync.assert_called_once_with( start_date="20240101", end_date="20240131", force_full=False, sample_size=3 )