Files
tick-stock-panel/backend/tests/test_custom_loader_install.py
T
dev 8519a2bd16 feat(ai): AI 生成自定义信号条件
- 新增 /api/signals/ai/generate 接口:自然语言描述 → 结构化信号条件
- 新增 custom_signals_ai 模块:组装提示词 + 解析校验 AI 返回的 JSON
- 复用 custom_signals.validate() 白名单安全闸门
- 新增输出 token 上限和上下文窗口设置
- 实现 AI 请求 max_tokens 钳制和输入预算检查
- 自定义信号右值字段名容错处理
- 增强 JSON 解析容错(尾随逗号、垃圾字符)
- 前端自定义信号对话框接入 AI 生成
2026-08-21 09:55:24 +08:00

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