mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 22:34:18 +08:00
- 新增 /api/signals/ai/generate 接口:自然语言描述 → 结构化信号条件 - 新增 custom_signals_ai 模块:组装提示词 + 解析校验 AI 返回的 JSON - 复用 custom_signals.validate() 白名单安全闸门 - 新增输出 token 上限和上下文窗口设置 - 实现 AI 请求 max_tokens 钳制和输入预算检查 - 自定义信号右值字段名容错处理 - 增强 JSON 解析容错(尾随逗号、垃圾字符) - 前端自定义信号对话框接入 AI 生成
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""loader.install_plugin 单元测试 — uv 安装必须显式指定目标环境。
|
|
|
|
回归场景: dev.ps1 直接跑 backend/.venv/Scripts/python.exe 而不 activate venv,
|
|
后端进程的 VIRTUAL_ENV 为空; 此时 `uv pip install -r ...` 不传 --python 会落到
|
|
PATH 上的基础解释器 (如 conda base, 常为只读) → 装错环境 + exit 2 (访问拒绝)。
|
|
修复: uv 命令显式传 `--python sys.executable` 锁定后端自身 venv, 与 pip 回退
|
|
路径 (sys.executable -m pip) 的目标一致。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
|
|
from app.data_providers.custom import loader
|
|
|
|
|
|
def _patch_uv_install(monkeypatch, returncodes):
|
|
"""替换 loader.shutil.which 与 subprocess.run, 捕获 uv 命令行。"""
|
|
calls: list[list[str]] = []
|
|
|
|
def fake_which(cmd, *_a, **_k):
|
|
return "/fake/uv" if cmd == "uv" else None
|
|
|
|
def fake_run(cmd, *_a, **_k):
|
|
calls.append(list(cmd))
|
|
code = returncodes.pop(0) if isinstance(returncodes, list) else returncodes
|
|
return subprocess.CompletedProcess(cmd, code)
|
|
|
|
monkeypatch.setattr(loader.shutil, "which", fake_which)
|
|
monkeypatch.setattr(loader.subprocess, "run", fake_run)
|
|
return calls
|
|
|
|
|
|
def test_install_plugin_uv_targets_running_interpreter(monkeypatch):
|
|
"""uv 分支必须传 --python sys.executable, 不能留给 uv 自动探测。"""
|
|
calls = _patch_uv_install(monkeypatch, [0])
|
|
|
|
ok, msg = loader.install_plugin("baostock")
|
|
assert ok, msg
|
|
assert calls, "应调用 uv"
|
|
uv_cmd = calls[0]
|
|
assert uv_cmd[0] == "/fake/uv"
|
|
assert "--python" in uv_cmd
|
|
idx = uv_cmd.index("--python")
|
|
assert uv_cmd[idx + 1] == sys.executable
|
|
|
|
|
|
def test_install_plugin_uv_retry_keeps_python_target(monkeypatch):
|
|
"""exit 2 → --no-config 重试时也必须保持 --python 目标。"""
|
|
calls = _patch_uv_install(monkeypatch, [2, 0])
|
|
|
|
ok, msg = loader.install_plugin("baostock")
|
|
assert ok, msg
|
|
assert len(calls) == 2, f"应触发一次重试, 实际 {len(calls)} 次"
|
|
for cmd in calls:
|
|
assert "--python" in cmd
|
|
idx = cmd.index("--python")
|
|
assert cmd[idx + 1] == sys.executable
|
|
|
|
|
|
def test_uninstall_plugin_uv_targets_running_interpreter(monkeypatch):
|
|
"""uv 卸载同样必须传 --python sys.executable, 否则会动到基础解释器。"""
|
|
calls: list[list[str]] = []
|
|
|
|
def fake_which(cmd, *_a, **_k):
|
|
return "/fake/uv" if cmd == "uv" else None
|
|
|
|
def fake_run(cmd, *_a, **_k):
|
|
calls.append(list(cmd))
|
|
return subprocess.CompletedProcess(cmd, 0)
|
|
|
|
monkeypatch.setattr(loader.shutil, "which", fake_which)
|
|
monkeypatch.setattr(loader.subprocess, "run", fake_run)
|
|
|
|
ok, msg = loader.uninstall_plugin("baostock")
|
|
assert ok, msg
|
|
assert calls, "应调用 uv"
|
|
uv_cmd = calls[0]
|
|
assert uv_cmd[0] == "/fake/uv"
|
|
assert "uninstall" in uv_cmd
|
|
assert "--python" in uv_cmd
|
|
idx = uv_cmd.index("--python")
|
|
assert uv_cmd[idx + 1] == sys.executable
|