test: enforce provider construction ownership
This commit is contained in:
@@ -220,6 +220,25 @@
|
||||
"runtime_role": "index observation fallback"
|
||||
}
|
||||
],
|
||||
"provider_construction": [
|
||||
{
|
||||
"client": "TushareClient",
|
||||
"owner": "backend/data/providers/tushare.py",
|
||||
"compatibility_fallback": "backend/features/market/service.py"
|
||||
},
|
||||
{
|
||||
"client": "IfindHttpClient",
|
||||
"owner": "backend/data/gateway.py"
|
||||
},
|
||||
{
|
||||
"client": "MarketChartClient",
|
||||
"owner": "backend/data/gateway.py"
|
||||
},
|
||||
{
|
||||
"client": "WebRealtimeAggregator",
|
||||
"owner": "backend/data/gateway.py"
|
||||
}
|
||||
],
|
||||
"numeric_normalization": [
|
||||
{
|
||||
"function": "finite_number",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import unittest
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from backend.data import (
|
||||
DataPolicyError,
|
||||
@@ -45,8 +47,6 @@ class DataGatewayTests(unittest.TestCase):
|
||||
self.assertIs(gateway.chart_data.ifind, gateway.ifind)
|
||||
|
||||
def test_server_has_no_direct_runtime_tushare_construction(self) -> None:
|
||||
from pathlib import Path
|
||||
|
||||
source = (
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "backend"
|
||||
@@ -57,6 +57,33 @@ class DataGatewayTests(unittest.TestCase):
|
||||
self.assertEqual(source.count("TushareClient(self.token)"), 1)
|
||||
self.assertIn("return gateway.tushare()", source)
|
||||
|
||||
def test_provider_construction_has_unique_declared_owners(self) -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
owners = {
|
||||
"EastmoneyChartClient": {"backend/data/gateway.py"},
|
||||
"IfindHttpClient": {"backend/data/gateway.py"},
|
||||
"IfindProvider": {"backend/data/gateway.py"},
|
||||
"MarketChartClient": {"backend/data/gateway.py"},
|
||||
"TushareClient": {"backend/features/market/service.py"},
|
||||
"TushareProvider": {"backend/data/gateway.py"},
|
||||
"WebRealtimeAggregator": {"backend/data/gateway.py"},
|
||||
}
|
||||
found = {name: set() for name in owners}
|
||||
for path in (root / "backend").rglob("*.py"):
|
||||
relative = path.relative_to(root).as_posix()
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, ast.Call):
|
||||
continue
|
||||
name = getattr(node.func, "id", None) or getattr(node.func, "attr", None)
|
||||
if name in found:
|
||||
found[name].add(relative)
|
||||
self.assertEqual(found, owners)
|
||||
provider_source = (root / "backend/data/providers/tushare.py").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
self.assertIn("client_factory: Callable[[str], TushareClient] = TushareClient", provider_source)
|
||||
|
||||
def test_quality_gate_accepts_matching_daily_evidence(self) -> None:
|
||||
timezone = market_timezone()
|
||||
now = datetime(2026, 7, 29, 16, 0, tzinfo=timezone)
|
||||
|
||||
@@ -155,6 +155,12 @@ def build() -> dict[str, Any]:
|
||||
{"provider": "eastmoney", "path": "backend/data/realtime.py", "runtime_role": "isolated realtime observation"},
|
||||
{"provider": "tencent", "path": "backend/data/realtime.py", "runtime_role": "index observation fallback"},
|
||||
],
|
||||
"provider_construction": [
|
||||
{"client": "TushareClient", "owner": "backend/data/providers/tushare.py", "compatibility_fallback": "backend/features/market/service.py"},
|
||||
{"client": "IfindHttpClient", "owner": "backend/data/gateway.py"},
|
||||
{"client": "MarketChartClient", "owner": "backend/data/gateway.py"},
|
||||
{"client": "WebRealtimeAggregator", "owner": "backend/data/gateway.py"},
|
||||
],
|
||||
"numeric_normalization": [
|
||||
{"function": "finite_number", "path": "backend/data/numbers.py"},
|
||||
{"function": "non_nan_number", "path": "backend/data/numbers.py"},
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
| CR-04 | 数值归一化策略 | 四个业务模块分别保留两组完全相同的数值转换函数体 | 由`backend/data/numbers.py`集中拥有两种既有语义,消费者保留原局部别名 | 已完成 |
|
||||
| CR-05 | 根级兼容入口 | 正式后端仍有五处通过迁移兼容模块反向导入规范实现 | 正式代码改用规范路径;兼容入口只服务原公开导入契约 | 已完成 |
|
||||
| CR-06 | 紧凑日期显示 | Tushare与选股引擎各保留一份完全相同的`YYYYMMDD`显示转换 | 由`bootstrap/config.py`拥有唯一格式策略,消费者保留原局部别名 | 已完成 |
|
||||
| CR-07 | 数据Provider组装 | 核查网关、容器和业务服务是否重复创建外部数据客户端 | 固化唯一创建位置及兼容例外,不改数据源语义 | 已完成 |
|
||||
|
||||
## CR-01验收口径
|
||||
|
||||
@@ -162,6 +163,28 @@
|
||||
本批基线为`xiaobai-reduction-05-compatibility-boundaries-20260801`;检查点为
|
||||
`xiaobai-reduction-06-date-formatting-20260801`。
|
||||
|
||||
## CR-07验收口径
|
||||
|
||||
- iFinD、图表、实时观察器和Provider适配器必须只在`build_data_gateway`创建,并由容器共享。
|
||||
- Tushare必须继续通过实时Token供应器按需创建,不能为了减少对象数量缓存过期Token。
|
||||
- 市场服务中为原版隔离测试桩保留的一处`TushareClient(self.token)`是明确兼容例外,不得被误判为
|
||||
第二条正式数据链路。
|
||||
- 不得合并Tushare、iFinD、东方财富和腾讯的传输、缓存、重试或降级逻辑。
|
||||
|
||||
## CR-07结果
|
||||
|
||||
- 全后端构造点扫描确认iFinD、MarketChart、东方财富图表和实时观察器均只有网关一个创建位置;
|
||||
`ApplicationContainer`暴露的是同一对象引用,没有第二份客户端。
|
||||
- Tushare Provider使用动态Token供应器,市场服务只有一处已登记测试兼容回退;本批没有发现可安全
|
||||
删除的生产实现,因此不为追求行数强行修改运行代码。
|
||||
- 架构清单新增Provider创建所有权,自动测试会在未来出现第二个未登记构造点时失败。
|
||||
- 候选322项、纯`app/`导出259项、24个JavaScript文件、API/架构注册表、Git空白检查和SQLite
|
||||
完整性检查通过;本批不涉及页面、CSS或浏览器行为。
|
||||
- 本批不修改请求频率、缓存、重试、Token更新、数据源选择、计算口径、API、数据库或前端。
|
||||
|
||||
本批基线为`xiaobai-reduction-06-date-formatting-20260801`;检查点为
|
||||
`xiaobai-reduction-07-provider-ownership-20260801`。
|
||||
|
||||
## 人工验收记录
|
||||
|
||||
- 2026-08-01:用户检查CR-02与CR-03运行结果,确认未发现明显异常。本记录仅表示本轮可见功能与
|
||||
|
||||
Reference in New Issue
Block a user