You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

273 lines
8.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
星耀数智(AmazingData)集成测试
测试覆盖:
- API端点测试
- 数据一致性检查
- 多数据源对比
- 完整工作流测试
"""
import unittest
import asyncio
import sys
import os
import json
from datetime import datetime, timedelta
from unittest.mock import Mock, patch, AsyncMock
# 添加项目根目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
class TestAPIEndpoints(unittest.TestCase):
"""测试API端点"""
def setUp(self):
"""测试前准备"""
self.base_url = 'http://localhost:8080/v1'
self.headers = {'X-Admin-Token': 'demo-api-key-2024'}
@patch('urllib.request.urlopen')
def test_adapters_endpoint(self, mock_urlopen):
"""测试适配器列表端点"""
# Mock响应
mock_response = Mock()
mock_response.read.return_value = json.dumps({
'code': 200,
'message': 'success',
'data': {
'adapters': [
{'name': 'amazingdata', 'status': 'active', 'type': 'stock'},
{'name': 'akshare', 'status': 'active', 'type': 'stock'}
]
}
}).encode()
mock_urlopen.return_value = mock_response
import urllib.request
req = urllib.request.Request(
f'{self.base_url}/admin/adapters',
headers=self.headers
)
response = urllib.request.urlopen(req, timeout=10)
data = json.loads(response.read().decode())
self.assertEqual(data['code'], 200)
self.assertIn('adapters', data['data'])
# 检查是否有amazingdata适配器
adapter_names = [a['name'] for a in data['data']['adapters']]
self.assertIn('amazingdata', adapter_names)
@patch('urllib.request.urlopen')
def test_source_status_endpoint(self, mock_urlopen):
"""测试数据源状态端点"""
mock_response = Mock()
mock_response.read.return_value = json.dumps({
'code': 200,
'message': 'success',
'data': {
'sources': [
{'id': 'amazingdata', 'name': '星耀数智', 'status': 'healthy'}
]
}
}).encode()
mock_urlopen.return_value = mock_response
import urllib.request
req = urllib.request.Request(
f'{self.base_url}/admin/source/status',
headers={'X-API-Key': 'demo-api-key-2024'}
)
response = urllib.request.urlopen(req, timeout=10)
data = json.loads(response.read().decode())
self.assertEqual(data['code'], 200)
class TestDataConsistency(unittest.TestCase):
"""测试数据一致性"""
def test_date_format_consistency(self):
"""测试日期格式一致性"""
from app.adapters.amazingdata_adapter import AmazingDataAdapter
adapter = AmazingDataAdapter()
# 测试不同格式的日期转换为统一格式
test_cases = [
('2024-01-01', 20240101),
('2024/01/01', 20240101),
('20240101', 20240101),
(20240101, 20240101),
]
for input_date, expected in test_cases:
result = adapter._format_date(input_date)
self.assertEqual(result, expected, f"Failed for {input_date}")
def test_symbol_format_consistency(self):
"""测试代码格式一致性"""
# 股票代码应该包含交易所后缀
valid_stock_codes = [
'000001.SZ',
'600000.SH',
'688001.SH',
'430001.BJ'
]
for code in valid_stock_codes:
# 检查格式: 代码.交易所
parts = code.split('.')
self.assertEqual(len(parts), 2, f"Invalid code format: {code}")
self.assertIn(parts[1], ['SH', 'SZ', 'BJ'], f"Invalid exchange: {parts[1]}")
class TestXYSZSpecificFeatures(unittest.TestCase):
"""测试星耀数智特有功能"""
def setUp(self):
"""测试前准备"""
self.adapter = None
try:
from app.adapters.amazingdata_adapter import AmazingDataAdapter
self.adapter_class = AmazingDataAdapter
except ImportError:
self.skipTest("AmazingData adapter not available")
def test_futures_exchange_mapping(self):
"""测试期货交易所映射"""
adapter = self.adapter_class()
# 测试上海期货交易所品种
self.assertEqual(adapter._get_futures_exchange('CU'), 'SHFE')
self.assertEqual(adapter._get_futures_exchange('AU'), 'SHFE')
# 测试大连商品交易所品种
self.assertEqual(adapter._get_futures_exchange('A'), 'DCE')
self.assertEqual(adapter._get_futures_exchange('M'), 'DCE')
# 测试郑州商品交易所品种
self.assertEqual(adapter._get_futures_exchange('CF'), 'CZCE')
self.assertEqual(adapter._get_futures_exchange('SR'), 'CZCE')
# 测试中金所品种
self.assertEqual(adapter._get_futures_exchange('IF'), 'CFFEX')
self.assertEqual(adapter._get_futures_exchange('IC'), 'CFFEX')
# 测试能源中心品种
self.assertEqual(adapter._get_futures_exchange('SC'), 'INE')
class TestWorkflow(unittest.IsolatedAsyncioTestCase):
"""测试完整工作流"""
async def asyncSetUp(self):
"""异步测试前准备"""
from app.adapters.amazingdata_adapter import AmazingDataAdapter
self.adapter = AmazingDataAdapter()
async def test_complete_workflow_mocked(self):
"""测试完整工作流Mock版本"""
# Mock所有依赖
self.adapter._ad = Mock()
self.adapter._base_data = Mock()
self.adapter._market_data = Mock()
self.adapter._info_data = Mock()
self.adapter._calendar = Mock()
self.adapter._is_logged_in = True
self.adapter._connected = True
# 1. 获取股票列表
self.adapter._base_data.get_code_list.return_value = [
'000001.SZ', '600000.SH'
]
import pandas as pd
self.adapter._base_data.get_code_info.return_value = pd.DataFrame({
'symbol': ['平安银行', '浦发银行']
}, index=['000001.SZ', '600000.SH'])
symbols = await self.adapter.fetch_symbols('stock')
self.assertEqual(len(symbols), 2)
# 2. 获取K线数据
kline_df = pd.DataFrame({
'open': [10.0], 'high': [11.0], 'low': [9.0],
'close': [10.5], 'volume': [10000], 'amount': [105000]
}, index=pd.to_datetime(['2024-01-01']))
self.adapter._market_data.query_kline.return_value = {
'000001.SZ': kline_df
}
klines = await self.adapter.fetch_klines(
symbol='000001.SZ',
start='20240101',
end='20240101',
freq='1d'
)
self.assertEqual(len(klines), 1)
# 3. 健康检查
self.adapter._base_data.get_code_list.return_value = ['000001.SZ']
health = await self.adapter.health_check()
self.assertTrue(health)
class TestErrorHandling(unittest.TestCase):
"""测试错误处理"""
def test_connection_error(self):
"""测试连接错误处理"""
from app.adapters.amazingdata_adapter import AmazingDataAdapter
adapter = AmazingDataAdapter()
# 测试未登录时调用方法
with self.assertRaises(RuntimeError) as context:
adapter._check_login()
self.assertIn('未连接到数据源', str(context.exception))
def test_invalid_date_format(self):
"""测试无效日期格式处理"""
from app.adapters.amazingdata_adapter import AmazingDataAdapter
adapter = AmazingDataAdapter()
# 测试无效输入
with self.assertRaises(ValueError):
adapter._format_date(None)
with self.assertRaises(ValueError):
adapter._format_date([])
with self.assertRaises(ValueError):
adapter._format_date({'date': '2024-01-01'})
class TestPerformanceRequirements(unittest.TestCase):
"""测试性能要求"""
def test_large_symbol_list_handling(self):
"""测试大批量代码处理"""
from app.adapters.amazingdata_adapter import AmazingDataAdapter
adapter = AmazingDataAdapter()
# 模拟5000只股票
large_code_list = [f'{i:06d}.SZ' for i in range(1, 5001)]
# 分批处理测试
batch_size = 50
batches = [large_code_list[i:i+batch_size] for i in range(0, len(large_code_list), batch_size)]
self.assertEqual(len(batches), 100)
self.assertEqual(len(batches[0]), 50)
if __name__ == '__main__':
unittest.main(verbosity=2)