Files
tick-stock-panel/backend/tests/test_task_registry.py
T
shy3130 ecb49cbaca release: v0.3.6 — 核心+扩展架构完善版
合并 0.3.6 分支全部内容 (36 提交, 详细历史见 0.3.6 分支):

■ 数据与插件
- TdxQuant 数据源: 通达信量化接入 (实时/五档/日K/复权/分钟), tdxaidata 收进默认依赖
- 扩展表两级拉取配方: 概念/行业出厂即带完整可拉配置, 可作自定义接入参考示例
- 能力门禁日志带生效源; 标的池解析只走 instruments 维表

■ 页面扩展化
- 11 个官方页面全量迁移为单文件扩展 (custom/*/extension.tsx), 删目录即卸载
- 运行时页面包系统: 打包产物上传即挂载, 免重建宿主
- 新装用户扩展页默认隐藏, 老用户升级菜单无感

■ AI
- Responses 协议接入; 会话历史与长任务跨设备同步 (服务端注册表)
- 助手头部快捷入口 + 悬浮球拖拽 zoom 坐标系修复 + 系统设置开关

■ 界面
- 设置「关于」子菜单: 版权/更新/打赏 + 开源许可弹窗与合规声明双保险
- 侧栏两态收起、数据/AI 助手快捷行 (同步呼吸光晕)、核心/扩展 ext 标记
- 界面缩放默认 90%; 菜单设置徽标 Swiss 化; 策略池「叠加」分组
- 情绪评分口径可配置; 财务画像 5 张子表手动拉取

■ 文档与其他
- README 重构「核心+扩展」主线 + 架构图重画; 开放端点数核实为 65
- 版本号 bump 0.3.6; tdx 物化库测试夹具补 macOS dylib
2026-10-13 13:40:17 +08:00

107 lines
4.3 KiB
Python

"""进行中任务注册表 (跨设备接回) 的服务与 API 测试。
契约: upsert 幂等 (同 id 更新不新增)、容量上限、孤儿 running 回收、
API 生命周期 (PUT/GET/DELETE) 与非法输入拒绝。存取均为小文件原子写。
"""
from __future__ import annotations
import time
from pathlib import Path
from types import SimpleNamespace
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from app.api import task_registry as task_registry_api
from app.services import task_registry
@pytest.fixture
def client(tmp_path: Path) -> TestClient:
app = FastAPI()
app.include_router(task_registry_api.router)
app.state.repo = SimpleNamespace(store=SimpleNamespace(data_dir=tmp_path))
return TestClient(app)
def test_upsert_idempotent_and_list_sorted(tmp_path: Path) -> None:
task_registry.upsert(tmp_path, "backtest:abc", kind="backtest", status="running", payload={"qs": "x=1"})
task_registry.upsert(
tmp_path, "backtest:abc", kind="backtest", status="done", payload={"qs": "x=1"}
)
task_registry.upsert(tmp_path, "mining:m1", kind="mining", status="running", payload={"run_id": "r1"})
items = task_registry.list_items(tmp_path)
assert [it["id"] for it in items] == ["mining:m1", "backtest:abc"] # updated_at 倒序
assert items[1]["status"] == "done" # 同 id 更新而非新增
assert items[1]["started_at"] > 0
only_bt = task_registry.list_items(tmp_path, kind="backtest")
assert [it["id"] for it in only_bt] == ["backtest:abc"]
def test_capacity_trim_keeps_newest(tmp_path: Path) -> None:
for i in range(task_registry.MAX_ITEMS + 5):
task_registry.upsert(
tmp_path, f"backtest:t{i}", kind="backtest", status="done"
)
time.sleep(0.001) # 保证 updated_at 严格递增
items = task_registry.list_items(tmp_path)
assert len(items) == task_registry.MAX_ITEMS
assert items[-1]["id"] == "backtest:t5" # t0..t4 最旧的被裁掉
assert items[0]["id"] == f"backtest:t{task_registry.MAX_ITEMS + 4}"
def test_orphan_running_pruned_on_read(tmp_path: Path) -> None:
import json
task_registry.upsert(tmp_path, "backtest:old", kind="backtest", status="running")
# 直接改文件把 updated_at 拨到 25h 前 (模拟进程重启遗留的孤儿 running)
p = tmp_path / "user_data" / task_registry._FILENAME
doc = json.loads(p.read_text(encoding="utf-8"))
doc["items"][0]["updated_at"] = time.time() - (task_registry.ORPHAN_RUNNING_SECONDS + 3600)
p.write_text(json.dumps(doc), encoding="utf-8")
items = task_registry.list_items(tmp_path)
assert items == [], "孤儿 running 项应在读取时被回收"
# 回收已落盘: 重复读不再触发
assert task_registry.list_items(tmp_path) == []
def test_api_lifecycle(client: TestClient) -> None:
assert client.get("/api/task-registry").json() == {"items": []}
r = client.put(
"/api/task-registry/backtest:k1",
json={"kind": "backtest", "status": "running", "label": "demo", "payload": {"qs": "a=1"}},
)
assert r.status_code == 200 and r.json()["item"]["id"] == "backtest:k1"
listed = client.get("/api/task-registry", params={"kind": "backtest"}).json()["items"]
assert len(listed) == 1 and listed[0]["label"] == "demo"
r = client.put(
"/api/task-registry/backtest:k1",
json={"kind": "backtest", "status": "done", "payload": {"qs": "a=1"}},
)
assert r.status_code == 200
assert client.get("/api/task-registry").json()["items"][0]["status"] == "done"
assert client.delete("/api/task-registry/backtest:k1").json()["removed"] is True
assert client.get("/api/task-registry").json()["items"] == []
assert client.delete("/api/task-registry/backtest:k1").json()["removed"] is False
def test_api_rejects_bad_input(client: TestClient) -> None:
assert client.put(
"/api/task-registry/bad id!", json={"kind": "backtest", "status": "running"}
).status_code == 400 # id 含非法字符
assert client.put(
"/api/task-registry/backtest:x", json={"kind": "nope", "status": "running"}
).status_code == 400 # 未知 kind
assert client.put(
"/api/task-registry/backtest:x", json={"kind": "backtest", "status": "weird"}
).status_code == 400 # 未知 status
assert client.get("/api/task-registry", params={"kind": "nope"}).status_code == 400