"""进程内节点注册表测试。 覆盖节点注册、全量注册、查询、进程内调用以及未注册节点的报错路径, 验证注册表作为调度器唯一调用入口的正确性。 """ import pytest from wov_app import registry from wov_sdk.models import InvokeRequest, InvokeResponse, NodeManifest def _echo_manifest() -> NodeManifest: """构造最小合法 echo 节点清单。""" return NodeManifest( id="echo", name="Echo", version="1.0.0", capability="echo", command=["python", "-m", "echo"], repo_dir="nodes", ) def test_register_and_list() -> None: """验证注册后可查询与列出节点,且按 ID 排序。""" registry.register(_echo_manifest(), lambda request: InvokeResponse(status="completed")) registry.register( NodeManifest( id="z-node", name="Z", version="1", capability="x", command=["python", "-m", "z"], repo_dir="nodes", ), lambda request: InvokeResponse(status="completed"), ) assert [node.id for node in registry.list_nodes()] == ["echo", "z-node"] assert registry.get_node("echo").capability == "echo" assert registry.get_node("missing") is None def test_register_validation() -> None: """验证非法 manifest 注册会被协议校验拒绝。""" invalid = _echo_manifest() invalid.id = "" with pytest.raises(ValueError): registry.register(invalid, lambda request: InvokeResponse(status="completed")) def test_register_all_loads_builtin_nodes() -> None: """验证 register_all 会加载 manifests/ 下全部内置节点。""" registry.register_all() ids = {node.id for node in registry.list_nodes()} assert { "echo", "ffmpeg-extract", "faster-whisper", "llm-translate", "vlm-ocr", "srt-to-dual-eye-ass", } <= ids def test_invoke_calls_handler() -> None: """验证 invoke 会把请求转发给注册的进程内处理器。""" captured = {} def handler(request: InvokeRequest) -> InvokeResponse: captured["run_id"] = request.run_id return InvokeResponse(status="completed", outputs={"text": "ok"}) registry.register(_echo_manifest(), handler) response = registry.invoke("echo", InvokeRequest(run_id="run_1", node_instance_id="")) assert response.status == "completed" assert response.outputs == {"text": "ok"} assert captured["run_id"] == "run_1" def test_invoke_unknown_node() -> None: """验证调用未注册节点时抛出 ValueError。""" with pytest.raises(ValueError, match="not registered"): registry.invoke("missing", InvokeRequest(run_id="run_1", node_instance_id="")) def test_invoke_logs_node_lifecycle(caplog) -> None: """验证 invoke 会记录节点的开始/完成/耗时日志(主进程可见)。""" registry.register( _echo_manifest(), lambda request: InvokeResponse(status="completed", outputs={"text": "ok"}) ) with caplog.at_level("INFO", logger="vrsub.node"): registry.invoke("echo", InvokeRequest(run_id="run_1", node_instance_id="")) assert any("节点 echo 开始" in record.message for record in caplog.records) assert any("节点 echo 完成" in record.message for record in caplog.records) def test_invoke_logs_node_failure(caplog) -> None: """验证节点返回 failed 时记录失败日志。""" registry.register( _echo_manifest(), lambda request: InvokeResponse(status="failed", error="boom") ) with caplog.at_level("INFO", logger="vrsub.node"): registry.invoke("echo", InvokeRequest(run_id="run_1", node_instance_id="")) assert any("节点 echo 失败" in record.message for record in caplog.records) def test_get_logger_idempotent() -> None: """验证日志器重复获取不会重复附加控制台处理器。""" from wov_app.logging import get_logger logger = get_logger("idempotent") handler_count = len(logger.handlers) again = get_logger("idempotent") assert again is logger assert len(again.handlers) == handler_count