核心变更: - engine.py(1781行)拆分为8个独立模块: lifecycle/security/deps/ datastore/pl_injector/watcher/signature/manager - 新增plugin-bridge: 事件总线 + 服务注册 + RPC通信 - 新增i18n: 国际化/多语言翻译支持 - 新增plugin-storage: 插件键值/文件存储 - 新增ws-api: WebSocket实时通信(pub/sub + 自定义处理器) - nodejs-adapter统一为Plugin ABC模式 Bug修复: - 修复load_all()中store_dir未定义崩溃 - 修复DependencyResolver入度计算(拓扑排序) - 修复PermissionError隐藏内置异常 - 修复CORS中间件头部未附加到响应 - 修复IntegrityChecker跳过__pycache__目录 - 修复版本号不一致(v2.0.0→v1.2.0) - 修复测试文件的Logger导入/路径/私有方法调用 - 修复context.py缺少typing导入 - 修复config.py STORE_DIR默认路径(./mods→./store) 测试覆盖: 14→91个测试, 全部通过
149 lines
4.1 KiB
Python
149 lines
4.1 KiB
Python
"""Tests for ws-api WebSocket plugin"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import time
|
|
import threading
|
|
import pytest
|
|
from pathlib import Path
|
|
|
|
PLUGIN_DIR = Path(__file__).parent.parent / "store" / "NebulaShell" / "ws-api"
|
|
sys.path.insert(0, str(PLUGIN_DIR))
|
|
|
|
import importlib.util
|
|
spec = importlib.util.spec_from_file_location("ws_api_main", str(PLUGIN_DIR / "main.py"))
|
|
main_module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(main_module)
|
|
WsApi = main_module.WsApi
|
|
|
|
|
|
class TestWsApi:
|
|
def test_lifecycle(self):
|
|
api = WsApi()
|
|
api.init()
|
|
api.start()
|
|
assert api._running is True
|
|
api.stop()
|
|
assert api._running is False
|
|
|
|
def test_get_info(self):
|
|
api = WsApi()
|
|
info = api.get_info()
|
|
assert "host" in info
|
|
assert "port" in info
|
|
assert "running" in info
|
|
assert "websockets_available" in info
|
|
|
|
def test_register_handler(self):
|
|
api = WsApi()
|
|
results = []
|
|
api.register_handler("custom", lambda data, ctx: results.append(data))
|
|
assert "custom" in api._handlers
|
|
|
|
def test_default_host_port(self):
|
|
api = WsApi()
|
|
assert api._host == "127.0.0.1"
|
|
assert api._port == 8081
|
|
|
|
|
|
class TestWsApiDispatch:
|
|
@pytest.mark.asyncio
|
|
async def test_ping_pong(self):
|
|
api = WsApi()
|
|
|
|
class FakeWs:
|
|
def __init__(self):
|
|
self.sent = []
|
|
self.remote_address = ("127.0.0.1", 12345)
|
|
|
|
async def send(self, msg):
|
|
self.sent.append(json.loads(msg))
|
|
|
|
ws = FakeWs()
|
|
await api._dispatch(ws, '{"type":"ping"}', "test")
|
|
assert len(ws.sent) == 1
|
|
assert ws.sent[0] == {"type": "pong"}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invalid_json(self):
|
|
api = WsApi()
|
|
|
|
class FakeWs:
|
|
def __init__(self):
|
|
self.sent = []
|
|
self.remote_address = ("127.0.0.1", 12345)
|
|
|
|
async def send(self, msg):
|
|
self.sent.append(json.loads(msg))
|
|
|
|
ws = FakeWs()
|
|
await api._dispatch(ws, "not json", "test")
|
|
assert len(ws.sent) == 1
|
|
assert ws.sent[0]["type"] == "error"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_subscribe(self):
|
|
api = WsApi()
|
|
|
|
class FakeWs:
|
|
def __init__(self):
|
|
self.sent = []
|
|
self.remote_address = ("127.0.0.1", 12345)
|
|
|
|
async def send(self, msg):
|
|
self.sent.append(json.loads(msg))
|
|
|
|
ws = FakeWs()
|
|
await api._dispatch(ws, '{"type":"subscribe","topic":"news"}', "test")
|
|
assert "news" in api._connections
|
|
assert len(api._connections["news"]) == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unsubscribe(self):
|
|
api = WsApi()
|
|
api._connections["test-topic"] = {"addr1"}
|
|
|
|
class FakeWs:
|
|
def __init__(self):
|
|
self.sent = []
|
|
self.remote_address = ("127.0.0.1", 12345)
|
|
|
|
async def send(self, msg):
|
|
self.sent.append(json.loads(msg))
|
|
|
|
ws = FakeWs()
|
|
await api._dispatch(ws, '{"type":"unsubscribe","topic":"test-topic"}', "addr1")
|
|
assert "test-topic" not in api._connections or len(api._connections["test-topic"]) == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_custom_handler(self):
|
|
api = WsApi()
|
|
results = []
|
|
|
|
def handler(data, ctx):
|
|
results.append((data, ctx))
|
|
return {"processed": True}
|
|
|
|
api.register_handler("my_action", handler)
|
|
|
|
class FakeWs:
|
|
def __init__(self):
|
|
self.sent = []
|
|
self.remote_address = ("127.0.0.1", 12345)
|
|
|
|
async def send(self, msg):
|
|
self.sent.append(json.loads(msg))
|
|
|
|
ws = FakeWs()
|
|
await api._dispatch(ws, '{"type":"my_action","value":42}', "test")
|
|
assert len(results) == 1
|
|
assert results[0][0]["value"] == 42
|
|
assert len(ws.sent) == 1
|
|
assert ws.sent[0]["type"] == "my_action_response"
|
|
assert ws.sent[0]["data"]["processed"] is True
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__, '-v'])
|