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
73 lines
2.7 KiB
Python
73 lines
2.7 KiB
Python
"""secrets.json 作为统一配置源的外部编辑契约。
|
|
|
|
用户可以直接用编辑器修改 secrets.json (AI + 数据源 Key 同文件), 保存后
|
|
下一次请求即读取最新内容 —— 不需要重启服务。这里锁定三条:
|
|
① file_path() 指向 data/user_data/secrets.json;
|
|
② 外部改写文件后 load()/get_ai_config() 立刻反映新值 (mtime+size 签名缓存失效);
|
|
③ GET /api/settings 暴露 secrets_path 供设置页展示。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from app import secrets_store
|
|
|
|
|
|
@pytest.fixture()
|
|
def data_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
|
from app.config import settings
|
|
|
|
monkeypatch.setattr(settings, "data_dir", tmp_path, raising=False)
|
|
yield tmp_path
|
|
|
|
|
|
def test_file_path_points_to_user_data_secrets(data_dir: Path) -> None:
|
|
assert secrets_store.file_path() == data_dir / "user_data" / "secrets.json"
|
|
|
|
|
|
def test_external_edit_is_picked_up_without_restart(data_dir: Path) -> None:
|
|
secrets_store.save({"ai_model": "old-model", "ai_base_url": "https://old.example/v1"})
|
|
assert secrets_store.get_ai_config("ai_model") == "old-model"
|
|
|
|
# 模拟用户编辑器直接覆写文件 (绕过 secrets_store.save)。
|
|
# 内容长度与 mtime 都变化, 签名缓存必然失效。
|
|
path = data_dir / "user_data" / "secrets.json"
|
|
path.write_text(json.dumps({
|
|
"ai_model": "brand-new-model",
|
|
"ai_base_url": "https://new.example/v1",
|
|
"ai_api_key": "sk-new",
|
|
}, indent=2), encoding="utf-8")
|
|
|
|
assert secrets_store.get_ai_config("ai_model") == "brand-new-model"
|
|
assert secrets_store.get_ai_config("ai_base_url") == "https://new.example/v1"
|
|
assert secrets_store.get_ai_key() == "sk-new"
|
|
|
|
|
|
def test_external_edit_with_same_size_still_reloads(data_dir: Path) -> None:
|
|
"""同长度改写: 签名含 mtime_ns, 不能因 size 相同而吃到旧缓存。"""
|
|
import os
|
|
import time
|
|
|
|
path = data_dir / "user_data" / "secrets.json"
|
|
secrets_store.save({"ai_model": "model-aaaa"})
|
|
assert secrets_store.get_ai_config("ai_model") == "model-aaaa"
|
|
|
|
path.write_text(json.dumps({"ai_model": "model-bbbb"}), encoding="utf-8")
|
|
# 强制 mtime 前进 (文件系统时间戳粒度可能吞掉快速连续写入)
|
|
future = time.time() + 5
|
|
os.utime(path, (future, future))
|
|
|
|
assert secrets_store.get_ai_config("ai_model") == "model-bbbb"
|
|
|
|
|
|
def test_get_settings_exposes_secrets_path(data_dir: Path) -> None:
|
|
from app.api import settings as settings_api
|
|
|
|
payload = settings_api.get_settings()
|
|
|
|
assert payload["secrets_path"].endswith("secrets.json")
|
|
assert str(data_dir) in payload["secrets_path"] or Path(payload["secrets_path"]).name == "secrets.json"
|