mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 17:54:15 +08:00
fix: allow stock-sdk plugin on Windows (#75)
Co-authored-by: tokenming <xtxo@users.noreply.github.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user