mirror of
https://github.com/shy3130/tick-stock-panel.git
synced 2026-11-02 10:26:13 +00:00
合并 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
98 lines
3.4 KiB
Python
98 lines
3.4 KiB
Python
"""AI 助手会话历史服务端存储 (跨设备同步) 的服务与 API 测试。
|
|
|
|
契约: GET 空结构、PUT 保存往返、结构裁剪 (无 id 会话剔除 / 消息截尾 200 /
|
|
会话上限 20)、损坏文件按空处理、API 挂载在 assistant 扩展路由下。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.custom.assistant import sessions as assistant_sessions
|
|
from app.custom.assistant.routes import build_router
|
|
|
|
|
|
@pytest.fixture
|
|
def client(tmp_path: Path) -> TestClient:
|
|
app = FastAPI()
|
|
app.include_router(build_router())
|
|
app.state.repo = SimpleNamespace(store=SimpleNamespace(data_dir=tmp_path))
|
|
return TestClient(app)
|
|
|
|
|
|
def _session(sid: str, n_msgs: int = 1) -> dict:
|
|
return {
|
|
"id": sid,
|
|
"title": f"会话{sid}",
|
|
"createdAt": 1700000000000,
|
|
"messages": [
|
|
{"id": f"m{i}", "role": "user", "content": f"hi {i}", "ts": i}
|
|
for i in range(n_msgs)
|
|
],
|
|
}
|
|
|
|
|
|
def test_api_roundtrip_and_empty_state(client: TestClient) -> None:
|
|
assert client.get("/api/custom/assistant/sessions").json() == {
|
|
"sessions": [],
|
|
"activeId": "",
|
|
}
|
|
|
|
r = client.put(
|
|
"/api/custom/assistant/sessions",
|
|
json={"sessions": [_session("s1"), _session("s2")], "activeId": "s2"},
|
|
)
|
|
assert r.status_code == 200 and r.json() == {"ok": True, "sessions": 2}
|
|
|
|
got = client.get("/api/custom/assistant/sessions").json()
|
|
assert [s["id"] for s in got["sessions"]] == ["s1", "s2"]
|
|
assert got["activeId"] == "s2"
|
|
|
|
|
|
def test_sanitize_trims_and_drops(client: TestClient, tmp_path: Path) -> None:
|
|
# 无 id/非字符串 id 的会话剔除
|
|
bad = [{"title": "no id"}, {"id": 123, "messages": []}]
|
|
# 消息超 200 截尾
|
|
long = _session("long", n_msgs=250)
|
|
# 会话超 20 截断 (服务层: API 层 max_length=20 会先拒绝更大的载荷)
|
|
many = [_session(f"s{i}") for i in range(25)]
|
|
|
|
n = assistant_sessions.save_sessions(
|
|
tmp_path, {"sessions": [*bad, long, *many], "activeId": "s0"}
|
|
)
|
|
assert n == assistant_sessions.MAX_SESSIONS
|
|
|
|
got = assistant_sessions.load_sessions(tmp_path)
|
|
ids = [s["id"] for s in got["sessions"]]
|
|
assert len(ids) == 20
|
|
assert "long" in ids # 输入顺序靠前, 保留
|
|
assert len(next(s for s in got["sessions"] if s["id"] == "long")["messages"]) == 200
|
|
|
|
# API 层: 结构非法的会话静默剔除, 合法照常保存
|
|
r = client.put(
|
|
"/api/custom/assistant/sessions",
|
|
json={"sessions": [*bad, _session("ok1")], "activeId": "ok1"},
|
|
)
|
|
assert r.status_code == 200 and r.json() == {"ok": True, "sessions": 1}
|
|
ids2 = [s["id"] for s in client.get("/api/custom/assistant/sessions").json()["sessions"]]
|
|
assert ids2 == ["ok1"]
|
|
|
|
|
|
def test_corrupted_file_treated_as_empty(tmp_path: Path) -> None:
|
|
p = tmp_path / "user_data" / "assistant_sessions.json"
|
|
p.parent.mkdir(parents=True)
|
|
p.write_text("{not json", encoding="utf-8")
|
|
assert assistant_sessions.load_sessions(tmp_path) == {"sessions": [], "activeId": ""}
|
|
|
|
|
|
def test_put_rejects_oversized_session_list(client: TestClient) -> None:
|
|
r = client.put(
|
|
"/api/custom/assistant/sessions",
|
|
json={"sessions": [_session(f"s{i}") for i in range(21)]},
|
|
)
|
|
assert r.status_code == 422 # 超过 max_length=20, 防御异常客户端
|