主站 - 新增 m0006 invite_codes 迁移;注册强制邀请码(首个管理员除外),消码与建号 同一事务,并发提交只有一个能成功 - 新增 /api/hub-admin/* 服务端点(共享 HUB_ADMIN_TOKEN,先于鉴权校验),供数据 中枢桥接读写会话/密码/模型池/会员/邀请码,并提供供应商模型列表拉取 - 前端:注册表单加邀请码(桌面 login、index.html、移动端);「系统管理」改为 「数据中枢」入口指向 8766,原模型池与会员管理分区移除,仅留「行情管理」; 随之清理陈旧 CSS 数据中枢 - 取消独立账号:删除 hub_admin/hub_sessions 与登录、改密、锁定逻辑,改为校验 主站 xiaobai_session,仅管理员可进,CSRF 由会话派生,危险操作二次确认走主站 - 控制台新增数据源凭证可编辑区(原有内容一项不删)、供应商制模型池(自动拉取 /models,失败退回卡内手动录入)、会员管理与邀请码页 - 日夜双主题:颜色收敛为同名 token 换值,SVG 改用 inline style 以吃到变量 自测 - 主站 verify_baseline 通过(498 项);数据中枢 235 项通过 - tools/verify_datahub_console.py 端到端跑通两服务真实对话; tools/verify_datahub_console_ui.py 浏览器跑通门禁/凭证/模型池/会员/主题/1030 窄屏 Co-authored-by: multica-agent <github@multica.ai>
81 lines
3.6 KiB
Python
81 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from datahub.adapters.tushare import TushareAdapter
|
|
from datahub.crypto import SecretVault
|
|
from datahub.hub import Hub
|
|
from datahub.settings import Settings
|
|
from tests.fixtures import TRADE_DATE, fake_transport
|
|
|
|
|
|
class ExtendedEodTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
key = SecretVault.generate_key()
|
|
settings = Settings(
|
|
host="127.0.0.1",
|
|
port=0,
|
|
encryption_key=key,
|
|
api_token="k" * 32,
|
|
tushare_token="tushare-secret",
|
|
db_path=Path(self.tmp.name) / "hub.db",
|
|
backup_dir=Path(self.tmp.name) / "backups",
|
|
scheduler_enabled=False,
|
|
quality={"daily_row_ratio": 0.5, "null_rate_max": 0.5, "list_limit_default": 5000, "list_limit_max": 5000},
|
|
)
|
|
adapter = TushareAdapter("tushare-secret", transport=fake_transport)
|
|
self.hub = Hub(settings, adapter=adapter)
|
|
self.hub.pipeline.ingest_reference(TRADE_DATE)
|
|
for dataset in ("daily", "valuation", "moneyflow", "auction", "index_daily"):
|
|
self.hub.pipeline.run_dataset(dataset, TRADE_DATE)
|
|
|
|
def tearDown(self) -> None:
|
|
self.hub.stop()
|
|
self.tmp.cleanup()
|
|
|
|
def test_extended_soft_datasets_publish_and_serve(self) -> None:
|
|
results = self.hub.pipeline.run_extended_soft(
|
|
("limit_events", "popularity", "dragon_tiger", "sector_daily"),
|
|
TRADE_DATE,
|
|
)
|
|
for name in ("limit_events", "popularity", "dragon_tiger", "sector_daily"):
|
|
self.assertEqual(results[name]["state"], "published", results[name])
|
|
api = self.hub.api
|
|
limits = api.handle("/v1/limit-events", {"date": [TRADE_DATE]})
|
|
self.assertGreaterEqual(len(limits["data"]), 1)
|
|
self.assertEqual(limits["meta"]["tier"], "official")
|
|
pop = api.handle("/v1/popularity", {"date": [TRADE_DATE], "source": ["ths"]})
|
|
self.assertEqual(pop["data"][0]["source"], "ths")
|
|
lhb = api.handle("/v1/dragon-tiger", {"date": [TRADE_DATE]})
|
|
self.assertEqual(lhb["data"][0]["hm_name"], "测试游资")
|
|
# hub stores 万元→元
|
|
self.assertEqual(lhb["data"][0]["buy_amount"], 10_000_000.0)
|
|
sectors = api.handle("/v1/sectors", {"date": [TRADE_DATE], "family": ["ths"]})
|
|
self.assertEqual(sectors["data"][0]["family"], "ths")
|
|
status = api.handle("/v1/datasets/status", {"date": [TRADE_DATE]})
|
|
names = {item["dataset"] for item in status["data"]}
|
|
self.assertTrue({"limit_events", "popularity", "dragon_tiger", "sector_daily"} <= names)
|
|
|
|
def test_partial_sector_families_are_retried_instead_of_skipped(self) -> None:
|
|
first = self.hub.pipeline.run_eod_batch_e(TRADE_DATE)
|
|
self.assertEqual(first["sector_daily"]["state"], "published")
|
|
batch_id = self.hub.pipeline.active_batch("sector_daily", TRADE_DATE)
|
|
with self.hub.db.write() as connection:
|
|
connection.execute(
|
|
"DELETE FROM eod_sector_daily WHERE trade_date = ? AND batch_id = ? AND family = 'sw'",
|
|
(TRADE_DATE, batch_id),
|
|
)
|
|
self.assertFalse(self.hub.pipeline.extended_publication_complete("sector_daily", TRADE_DATE))
|
|
|
|
retried = self.hub.pipeline.run_eod_batch_e(TRADE_DATE)
|
|
|
|
self.assertEqual(retried["sector_daily"]["state"], "published")
|
|
self.assertTrue(self.hub.pipeline.extended_publication_complete("sector_daily", TRADE_DATE))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|