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.

930 lines
39 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.

"""测试服务 - 对应Go的internal/service/test.go"""
import asyncio
import json
from datetime import datetime, timedelta
from typing import List, Optional
from threading import RLock
import httpx
import websockets
import pandas as pd
from app.models import (
APITestListData, APITestCategory, APITestCase,
APITestRequest, APITestResult,
WSTestListData, WSTestCase, WSTestRequest, WSTestResult, WSMessage,
TestHistoryRequest, TestHistoryData
)
from app.core.logger import info, error
class TestService:
"""测试服务"""
def __init__(self):
self.lock = RLock()
self.api_history: List[APITestResult] = []
self.internal_history: List[APITestResult] = []
self.ws_history: List[WSTestResult] = []
self.history_size = 100
def get_api_test_list(self) -> APITestListData:
"""获取API测试列表对外接口"""
# 固定交易时间2026年3月2日到2026年3月6日
test_start = datetime(2026, 3, 2)
test_end = datetime(2026, 3, 6)
categories = [
APITestCategory(
name="【对外】股票数据接口",
items=[
APITestCase(
id="stock_klines",
name="查询股票K线",
method="GET",
path="/v1/stock/klines/{symbol}",
description="查询指定股票的K线数据",
params={
"symbol": "000001.SZ",
"start": test_start.strftime("%Y%m%d"),
"end": test_end.strftime("%Y%m%d"),
"freq": "1d",
"adjust": "qfq"
}
),
APITestCase(
id="stock_symbols",
name="查询股票列表",
method="GET",
path="/v1/stock/symbols",
description="获取所有可用股票标的",
params={"page": "1", "size": "20"}
),
APITestCase(
id="stock_batch",
name="批量查询股票K线",
method="POST",
path="/v1/stock/klines/batch",
description="批量查询多只股票K线",
body={
"symbols": ["000001.SZ", "000002.SZ"],
"start": test_start.strftime("%Y%m%d"),
"end": test_end.strftime("%Y%m%d"),
"freq": "1d"
}
),
APITestCase(
id="stock_calendar",
name="查询股票交易日历",
method="GET",
path="/v1/stock/trading-dates",
description="查询股票交易日历",
params={
"start": test_start.strftime("%Y%m%d"),
"end": test_end.strftime("%Y%m%d")
}
),
]
),
APITestCategory(
name="【对外】期货数据接口",
items=[
APITestCase(
id="futures_klines",
name="查询期货K线",
method="GET",
path="/v1/futures/klines/{symbol}",
description="查询指定期货合约的K线数据",
params={
"symbol": "CU2504.SHFE",
"start": test_start.strftime("%Y%m%d"),
"end": test_end.strftime("%Y%m%d"),
"freq": "1d"
}
),
APITestCase(
id="futures_symbols",
name="查询期货列表",
method="GET",
path="/v1/futures/symbols",
description="获取所有可用期货标的",
params={"page": "1", "size": "20"}
),
APITestCase(
id="futures_batch",
name="批量查询期货K线",
method="POST",
path="/v1/futures/klines/batch",
description="批量查询多个期货合约K线",
body={
"symbols": ["CU2504.SHFE", "RB2505.SHFE"],
"start": test_start.strftime("%Y%m%d"),
"end": test_end.strftime("%Y%m%d"),
"freq": "1d"
}
),
APITestCase(
id="futures_contracts",
name="查询合约列表",
method="GET",
path="/v1/futures/contracts",
description="根据品种查询可交易合约",
params={"underlying": "CU", "exchange": "SHFE"}
),
APITestCase(
id="futures_calendar",
name="查询期货交易日历",
method="GET",
path="/v1/futures/trading-dates",
description="查询期货交易日历",
params={
"start": test_start.strftime("%Y%m%d"),
"end": test_end.strftime("%Y%m%d")
}
),
]
),
APITestCategory(
name="【对外】管理接口",
items=[
APITestCase(
id="admin_health",
name="健康检查",
method="GET",
path="/v1/admin/health",
description="检查服务健康状态",
params={}
),
APITestCase(
id="admin_source_status",
name="数据源状态",
method="GET",
path="/v1/admin/source/status",
description="获取当前数据源状态",
params={}
),
APITestCase(
id="admin_source_switch",
name="切换数据源",
method="POST",
path="/v1/admin/source/switch",
description="切换到指定数据源amazingdata",
body={
"asset_class": "all",
"source": "amazingdata",
"sync_backfill": False
}
),
APITestCase(
id="admin_system_status",
name="系统状态",
method="GET",
path="/v1/admin/system/status",
description="获取系统运行状态和资源使用情况",
params={}
),
]
),
APITestCategory(
name="【对外】数据同步接口",
items=[
APITestCase(
id="admin_data_sync_full",
name="全量数据同步",
method="POST",
path="/v1/admin/data/sync",
description="手动触发全量数据同步(基础+行情+财务)",
body={
"symbols": ["000001.SZ", "600519.SH"],
"sync_type": "full",
"start_date": "20240301",
"end_date": "20240310",
"asset_class": "stock"
}
),
APITestCase(
id="admin_data_sync_base",
name="同步基础K线数据",
method="POST",
path="/v1/admin/data/sync",
description="仅同步OHLCV基础数据",
body={
"symbols": ["000001.SZ"],
"sync_type": "base",
"freq": "1d",
"start_date": "20240301",
"end_date": "20240310",
"asset_class": "stock"
}
),
APITestCase(
id="admin_data_sync_quote",
name="同步行情指标数据",
method="POST",
path="/v1/admin/data/sync",
description="同步均线/MACD/涨跌幅",
body={
"symbols": ["000001.SZ"],
"sync_type": "quote",
"start_date": "20240301",
"end_date": "20240310",
"asset_class": "stock"
}
),
APITestCase(
id="admin_data_sync_finance",
name="同步财务数据",
method="POST",
path="/v1/admin/data/sync",
description="同步市值/股本/利润",
body={
"symbols": ["000001.SZ"],
"sync_type": "finance",
"start_date": "20240301",
"end_date": "20240310",
"asset_class": "stock"
}
),
APITestCase(
id="admin_data_sync_incremental",
name="增量数据同步",
method="POST",
path="/v1/admin/data/sync/incremental",
description="触发增量同步最近30天",
body=["000001.SZ", "600519.SH"]
),
]
),
]
return APITestListData(categories=categories, base_url="")
def get_internal_test_list(self) -> APITestListData:
"""获取内部接口测试列表(对内接口 - SDK封装层"""
categories = [
APITestCategory(
name="【对内】市场数据接口 (_market_data)",
items=[
APITestCase(
id="internal_market_query_kline",
name="SDK: query_kline",
method="INTERNAL",
path="AmazingDataAdapter._internal.market.query_kline",
description="查询K线数据内部SDK调用",
params={"symbol": "000001.SZ", "period": "1d"}
),
APITestCase(
id="internal_market_query_snapshot",
name="SDK: query_snapshot",
method="INTERNAL",
path="AmazingDataAdapter._internal.market.query_snapshot",
description="查询快照数据内部SDK调用",
params={"symbol": "000001.SZ"}
),
]
),
APITestCategory(
name="【对内】基础数据接口 (_base_data)",
items=[
APITestCase(
id="internal_base_get_code_list",
name="SDK: get_code_list",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_code_list",
description="获取股票代码列表内部SDK调用",
params={}
),
APITestCase(
id="internal_base_get_future_code_list",
name="SDK: get_future_code_list",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_future_code_list",
description="获取期货代码列表内部SDK调用",
params={}
),
APITestCase(
id="internal_base_get_code_info",
name="SDK: get_code_info",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_code_info",
description="获取代码信息内部SDK调用",
params={}
),
APITestCase(
id="internal_base_get_calendar",
name="SDK: get_calendar",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_calendar",
description="获取交易日历内部SDK调用",
params={}
),
APITestCase(
id="internal_base_get_adj_factor",
name="SDK: get_adj_factor",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_adj_factor",
description="获取复权因子内部SDK调用",
params={}
),
APITestCase(
id="internal_base_get_etf_pcf",
name="SDK: get_etf_pcf",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_etf_pcf",
description="获取ETF申赎数据内部SDK调用",
params={}
),
]
),
APITestCategory(
name="【对内】股本股东接口 (_info_data)",
items=[
APITestCase(
id="internal_info_get_equity_structure",
name="SDK: get_equity_structure",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_equity_structure",
description="获取股本结构内部SDK调用",
params={"symbol": "000001.SZ"}
),
APITestCase(
id="internal_info_get_share_holder",
name="SDK: get_share_holder",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_share_holder",
description="获取股东数据内部SDK调用",
params={"symbol": "000001.SZ"}
),
APITestCase(
id="internal_info_get_holder_num",
name="SDK: get_holder_num",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_holder_num",
description="获取股东户数内部SDK调用",
params={"symbol": "000001.SZ"}
),
]
),
APITestCategory(
name="【对内】财务报表接口 (_info_data)",
items=[
APITestCase(
id="internal_info_get_income",
name="SDK: get_income",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_income",
description="获取利润表内部SDK调用",
params={"symbol": "000001.SZ"}
),
APITestCase(
id="internal_info_get_balance_sheet",
name="SDK: get_balance_sheet",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_balance_sheet",
description="获取资产负债表内部SDK调用",
params={"symbol": "000001.SZ"}
),
APITestCase(
id="internal_info_get_cash_flow",
name="SDK: get_cash_flow",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_cash_flow",
description="获取现金流量表内部SDK调用",
params={"symbol": "000001.SZ"}
),
]
),
APITestCategory(
name="【对内】市场状态接口 (_info_data)",
items=[
APITestCase(
id="internal_info_get_history_stock_status",
name="SDK: get_history_stock_status",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_history_stock_status",
description="获取历史股票状态(涨停/跌停/ST/停牌内部SDK调用",
params={"symbol": "000001.SZ"}
),
APITestCase(
id="internal_info_get_margin_summary",
name="SDK: get_margin_summary",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_margin_summary",
description="获取融资融券汇总内部SDK调用",
params={}
),
APITestCase(
id="internal_info_get_margin_detail",
name="SDK: get_margin_detail",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_margin_detail",
description="获取融资融券明细内部SDK调用",
params={"symbol": "000001.SZ"}
),
]
),
APITestCategory(
name="【对内】特色数据接口 (_info_data)",
items=[
APITestCase(
id="internal_info_get_long_hu_bang",
name="SDK: get_long_hu_bang",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_long_hu_bang",
description="获取龙虎榜数据内部SDK调用",
params={}
),
APITestCase(
id="internal_info_get_block_trading",
name="SDK: get_block_trading",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_block_trading",
description="获取大宗交易数据内部SDK调用",
params={}
),
APITestCase(
id="internal_info_get_index_constituent",
name="SDK: get_index_constituent",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_index_constituent",
description="获取指数成分股内部SDK调用",
params={"index": "000300.SH"}
),
APITestCase(
id="internal_info_get_index_weight",
name="SDK: get_index_weight",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_index_weight",
description="获取指数权重内部SDK调用",
params={"index": "000300.SH"}
),
]
),
APITestCategory(
name="【对内】基金可转债接口 (_info_data)",
items=[
APITestCase(
id="internal_info_get_fund_share",
name="SDK: get_fund_share",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_fund_share",
description="获取基金份额内部SDK调用",
params={}
),
APITestCase(
id="internal_info_get_kzz_issuance",
name="SDK: get_kzz_issuance",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_kzz_issuance",
description="获取可转债发行数据内部SDK调用",
params={}
),
]
),
]
return APITestListData(categories=categories, base_url="")
async def run_api_test(self, base_url: str, req: APITestRequest) -> APITestResult:
"""执行对外API测试"""
# 获取测试用例
test_list = self.get_api_test_list()
test_case = None
for cat in test_list.categories:
for item in cat.items:
if item.id == req.id:
test_case = item
break
if test_case:
break
if not test_case:
raise ValueError(f"Test case not found: {req.id}")
# 合并参数
params = dict(test_case.params)
if req.params:
params.update(req.params)
# 构建URL
url = base_url + test_case.path
for k, v in params.items():
url = url.replace(f"{{{k}}}", str(v))
# 添加查询参数
if test_case.method == "GET" and params:
query_parts = []
for k, v in params.items():
if f"{{{k}}}" not in test_case.path:
query_parts.append(f"{k}={v}")
if query_parts:
url += "?" + "&".join(query_parts)
# 准备请求体
body = req.body if req.body is not None else test_case.body
# 执行请求
start_time = datetime.now()
async with httpx.AsyncClient() as client:
try:
headers = {"X-API-Key": "test-api-key"}
if test_case.method == "GET":
response = await client.get(url, headers=headers, timeout=30)
elif test_case.method == "POST":
response = await client.post(
url, json=body, headers=headers, timeout=30
)
else:
raise ValueError(f"Unsupported method: {test_case.method}")
latency = int((datetime.now() - start_time).total_seconds() * 1000)
result = APITestResult(
id=int(datetime.now().timestamp()),
case_id=req.id,
name=test_case.name,
success=200 <= response.status_code < 300,
status_code=response.status_code,
latency=latency,
request={
"method": test_case.method,
"url": url,
"body": body
},
response=response.json() if response.headers.get("content-type", "").startswith("application/json") else response.text,
timestamp=datetime.now()
)
self._add_api_history(result)
return result
except Exception as e:
latency = int((datetime.now() - start_time).total_seconds() * 1000)
result = APITestResult(
id=int(datetime.now().timestamp()),
case_id=req.id,
name=test_case.name,
success=False,
latency=latency,
request={
"method": test_case.method,
"url": url,
"body": body
},
error=str(e),
timestamp=datetime.now()
)
self._add_api_history(result)
return result
async def run_internal_test(self, adapter, req: APITestRequest) -> APITestResult:
"""执行内部接口测试SDK封装层"""
from app.adapters.amazingdata_adapter import AmazingDataAdapter
start_time = datetime.now()
try:
# 确保已连接
if not adapter._is_logged_in:
raise RuntimeError("Adapter not connected")
# 根据测试ID调用对应的内部接口
result_data = None
error_msg = None
if req.id == "internal_market_query_kline":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.market.query_kline(
code_list=[symbol],
begin_date=20240301,
end_date=20240310,
period=10000 # SDK只支持10000日线
)
elif req.id == "internal_market_query_snapshot":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.market.query_snapshot(
code_list=[symbol],
begin_date=20240301,
end_date=20240301
)
elif req.id == "internal_base_get_code_list":
from app.adapters.amazingdata_adapter import SecurityType
result_data = adapter._internal.base.get_code_list(
security_type=SecurityType.STOCK_A.value
)
elif req.id == "internal_base_get_future_code_list":
from app.adapters.amazingdata_adapter import SecurityType
result_data = adapter._internal.base.get_future_code_list(
security_type=SecurityType.FUTURE.value
)
elif req.id == "internal_base_get_code_info":
from app.adapters.amazingdata_adapter import SecurityType
result_data = adapter._internal.base.get_code_info(
security_type=SecurityType.STOCK_A.value
)
elif req.id == "internal_base_get_calendar":
result_data = adapter._internal.base.get_calendar(market="SH")
elif req.id == "internal_base_get_adj_factor":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.base.get_adj_factor(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_base_get_etf_pcf":
result_data = adapter._internal.base.get_etf_pcf(
code_list=["510050.SH"]
)
elif req.id == "internal_info_get_equity_structure":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_equity_structure(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_share_holder":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_share_holder(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_holder_num":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_holder_num(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_income":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_income(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_balance_sheet":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_balance_sheet(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_cash_flow":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_cash_flow(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_history_stock_status":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_history_stock_status(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache,
begin_date=20240301,
end_date=20240310
)
elif req.id == "internal_info_get_margin_summary":
result_data = adapter._internal.info.get_margin_summary(
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_margin_detail":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_margin_detail(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_long_hu_bang":
result_data = adapter._internal.info.get_long_hu_bang(
code_list=["000001.SZ"],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_block_trading":
result_data = adapter._internal.info.get_block_trading(
code_list=["000001.SZ"],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_index_constituent":
index_code = req.params.get("index", "000300.SH")
result_data = adapter._internal.info.get_index_constituent(
code_list=[index_code],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_index_weight":
index_code = req.params.get("index", "000300.SH")
result_data = adapter._internal.info.get_index_weight(
code_list=[index_code],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_fund_share":
result_data = adapter._internal.info.get_fund_share(
code_list=["510050.SH"],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_kzz_issuance":
result_data = adapter._internal.info.get_kzz_issuance(
code_list=["110043.SH"],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
else:
raise ValueError(f"Unknown internal test case: {req.id}")
latency = int((datetime.now() - start_time).total_seconds() * 1000)
# 检查结果
has_data = result_data is not None
if isinstance(result_data, (dict, list)):
has_data = len(result_data) > 0
elif isinstance(result_data, pd.DataFrame):
has_data = not result_data.empty
result = APITestResult(
id=int(datetime.now().timestamp()),
case_id=req.id,
name=req.id.replace("internal_", "").replace("_", ":"),
success=has_data,
status_code=200 if has_data else 204,
latency=latency,
request={"params": req.params},
response={"data_count": len(result_data) if hasattr(result_data, '__len__') else 1} if has_data else None,
error=None if has_data else "No data returned",
timestamp=datetime.now()
)
self._add_internal_history(result)
return result
except Exception as e:
latency = int((datetime.now() - start_time).total_seconds() * 1000)
result = APITestResult(
id=int(datetime.now().timestamp()),
case_id=req.id,
name=req.id.replace("internal_", "").replace("_", ":"),
success=False,
latency=latency,
request={"params": req.params},
error=str(e),
timestamp=datetime.now()
)
self._add_internal_history(result)
return result
def get_ws_test_list(self) -> WSTestListData:
"""获取WebSocket测试列表"""
cases = [
WSTestCase(
id="ws_subscribe_stock",
name="订阅股票行情",
description="订阅单只股票实时行情",
action="subscribe",
symbols=["000001.SZ"]
),
WSTestCase(
id="ws_subscribe_futures",
name="订阅期货行情",
description="订阅单个期货合约实时行情",
action="subscribe",
symbols=["CU2504.SHFE"]
),
WSTestCase(
id="ws_subscribe_multi",
name="批量订阅",
description="同时订阅多个标的",
action="subscribe",
symbols=["000001.SZ", "000002.SZ", "CU2504.SHFE"]
),
]
return WSTestListData(cases=cases, ws_url="")
async def run_ws_test(self, ws_url: str, req: WSTestRequest) -> WSTestResult:
"""执行WebSocket测试"""
test_list = self.get_ws_test_list()
test_case = None
for item in test_list.cases:
if item.id == req.id:
test_case = item
break
if not test_case:
raise ValueError(f"Test case not found: {req.id}")
symbols = req.symbols if req.symbols else test_case.symbols
result = WSTestResult(
id=f"ws_{int(datetime.now().timestamp())}",
case_id=req.id,
timestamp=datetime.now(),
messages=[]
)
start_time = datetime.now()
try:
async with websockets.connect(
ws_url,
extra_headers={"X-API-Key": "test-api-key"}
) as ws:
result.latency = int((datetime.now() - start_time).total_seconds() * 1000)
result.success = True
msg = {
"action": test_case.action,
"symbols": symbols
}
await ws.send(json.dumps(msg))
for _ in range(3):
try:
msg_data = await asyncio.wait_for(ws.recv(), timeout=5)
result.messages.append(WSMessage(
type="received",
data=json.loads(msg_data),
timestamp=datetime.now()
))
except asyncio.TimeoutError:
break
except Exception as e:
result.latency = int((datetime.now() - start_time).total_seconds() * 1000)
result.success = False
result.error = str(e)
self._add_ws_history(result)
return result
def get_test_history(self, req: TestHistoryRequest) -> TestHistoryData:
"""获取测试历史"""
with self.lock:
limit = req.limit or 20
api_tests = []
ws_tests = []
if not req.type or req.type == "api":
api_tests = self.api_history[-limit:]
if not req.type or req.type == "ws":
ws_tests = self.ws_history[-limit:]
return TestHistoryData(api_tests=api_tests, ws_tests=ws_tests)
def _add_api_history(self, result: APITestResult):
"""添加API测试历史"""
with self.lock:
self.api_history.append(result)
if len(self.api_history) > self.history_size:
self.api_history = self.api_history[-self.history_size:]
def _add_internal_history(self, result: APITestResult):
"""添加内部接口测试历史"""
with self.lock:
self.internal_history.append(result)
if len(self.internal_history) > self.history_size:
self.internal_history = self.internal_history[-self.history_size:]
def _add_ws_history(self, result: WSTestResult):
"""添加WebSocket测试历史"""
with self.lock:
self.ws_history.append(result)
if len(self.ws_history) > self.history_size:
self.ws_history = self.ws_history[-self.history_size:]