fix: allow stock-sdk plugin on Windows (#75)

Co-authored-by: tokenming <xtxo@users.noreply.github.com>
This commit is contained in:
xtxo
2026-07-08 14:19:52 +08:00
committed by GitHub
co-authored by tokenming
parent 8e56f3efb8
commit f273349cc8
3 changed files with 68 additions and 8 deletions
+2 -2
View File
@@ -25,7 +25,7 @@
* realtime/instruments 是全市场枚举,由 code + marketId 反推后缀。
*/
import { createRequire } from 'node:module'
import { pathToFileURL } from 'node:url'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { execSync } from 'node:child_process'
import path from 'node:path'
@@ -36,7 +36,7 @@ import path from 'node:path'
*/
async function loadSDK() {
const require = createRequire(import.meta.url)
const scriptDir = path.dirname(new URL(import.meta.url).pathname)
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
// 候选 node_modules 目录(按优先级)
const nmDirs = [path.join(scriptDir, 'node_modules')]
for (const p of (process.env.NODE_PATH || '').split(path.delimiter).filter(Boolean)) nmDirs.push(p)
+2
View File
@@ -53,6 +53,8 @@ def run_job(job: dict, timeout: int = DEFAULT_TIMEOUT) -> dict:
input=payload,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=timeout,
cwd=str(_HERE),
)
+64 -6
View File
@@ -6,9 +6,13 @@
from __future__ import annotations
import datetime as dt
import json
import shutil
import subprocess
import polars as pl
from app.plugins.stocksdk import bridge
from app.plugins.stocksdk import provider as sp
from app.plugins.stocksdk.provider import StockSDKProvider
@@ -16,7 +20,7 @@ from app.plugins.stocksdk.provider import StockSDKProvider
def _patch_run_job(monkeypatch, mapping):
"""mapping: op -> payload dict(将作为 run_job 返回值)。"""
def fake(job, timeout=None): # noqa: ARG001
def fake(job, timeout=None):
return mapping[job["op"]]
monkeypatch.setattr(sp.bridge, "run_job", fake)
@@ -106,7 +110,7 @@ def test_empty_symbols_returns_empty():
def test_bridge_error_degrades_to_empty(monkeypatch):
def boom(job, timeout=None): # noqa: ARG001
def boom(job, timeout=None):
raise sp.bridge.StockSDKBridgeError("node missing")
monkeypatch.setattr(sp.bridge, "run_job", boom)
@@ -115,6 +119,60 @@ def test_bridge_error_degrades_to_empty(monkeypatch):
assert StockSDKProvider().get_instruments("stock") == []
def test_bridge_uses_utf8_error_tolerant_subprocess(monkeypatch):
calls = []
class Result:
returncode = 0
stdout = json.dumps({"ok": True, "op": "ping"})
stderr = ""
monkeypatch.setattr(bridge, "_node_bin", lambda: "node")
def fake_run(*args, **kwargs):
calls.append((args, kwargs))
return Result()
monkeypatch.setattr(subprocess, "run", fake_run)
assert bridge.run_job({"op": "ping"})["ok"] is True
kwargs = calls[0][1]
assert kwargs["encoding"] == "utf-8"
assert kwargs["errors"] == "replace"
def test_bridge_mjs_resolves_local_stock_sdk_on_windows_path(tmp_path):
if shutil.which("node") is None:
raise AssertionError("node is required for stock-sdk bridge path regression test")
bridge_path = tmp_path / "bridge.mjs"
shutil.copyfile(bridge._BRIDGE_MJS, bridge_path)
pkg_dir = tmp_path / "node_modules" / "stock-sdk"
pkg_dir.mkdir(parents=True)
(pkg_dir / "package.json").write_text(
json.dumps({"name": "stock-sdk", "type": "module", "main": "index.js"}),
encoding="utf-8",
)
(pkg_dir / "index.js").write_text(
"export class StockSDK { static version = 'fake-local' }\n",
encoding="utf-8",
)
proc = subprocess.run(
["node", str(bridge_path)],
input=json.dumps({"op": "ping"}),
capture_output=True,
text=True,
encoding="utf-8",
timeout=20,
)
assert proc.returncode == 0
result = json.loads(proc.stdout)
assert result == {"ok": True, "op": "ping", "version": "fake-local"}
def test_plugin_discovered_in_loader():
"""插件被发现并记录状态 (即使依赖没装, 不可用)。"""
from app.data_providers import custom as cs
@@ -133,12 +191,12 @@ def test_plugin_discovered_in_loader():
def test_plugin_registered_when_available(monkeypatch):
"""依赖可用时, 插件注册进 _PROVIDERS 并可路由。"""
from app.data_providers import custom as cs
from app.data_providers.custom import loader as L
from app.data_providers.custom import loader
# mock availability 返回 (True, "ok")
monkeypatch.setattr(L, "_call_check", lambda ref: (True, "ok"))
monkeypatch.setattr(L, "_load_entry", _load_stocksdk_entry)
L._load_builtin_plugins()
monkeypatch.setattr(loader, "_call_check", lambda ref: (True, "ok"))
monkeypatch.setattr(loader, "_load_entry", _load_stocksdk_entry)
loader._load_builtin_plugins()
assert "stocksdk" in cs.names()
assert cs.is_custom_provider("stocksdk")