From 550596f89af410df0f735671f56bc23395d798fc Mon Sep 17 00:00:00 2001 From: leefer Date: Sun, 2 Aug 2026 00:42:46 +0800 Subject: [PATCH] test: enforce provider construction ownership --- config/architecture-inventory.json | 19 ++++++++++++++++ tests/test_data_gateway.py | 31 +++++++++++++++++++++++++-- tools/build_architecture_inventory.py | 6 ++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/config/architecture-inventory.json b/config/architecture-inventory.json index 5941ac4..625fdeb 100644 --- a/config/architecture-inventory.json +++ b/config/architecture-inventory.json @@ -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", diff --git a/tests/test_data_gateway.py b/tests/test_data_gateway.py index 32cfe04..536a0b5 100644 --- a/tests/test_data_gateway.py +++ b/tests/test_data_gateway.py @@ -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) diff --git a/tools/build_architecture_inventory.py b/tools/build_architecture_inventory.py index f89a766..f5b84c6 100644 --- a/tools/build_architecture_inventory.py +++ b/tools/build_architecture_inventory.py @@ -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"},