新增了插件相关的完整领域模型、应用服务、基础设施实现,包括: 1. 插件状态、注册模式、来源等基础枚举和数据结构 2. 插件清单解析、发现、加载工具类 3. 插件注册表领域服务和内存存储实现 4. 插件相关的命令、查询、事件定义 5. 插件REST API接口和DTO映射 6. 集成了原有通道适配器到插件系统 7. 新增内置插件注册和自动发现能力
88 lines
3.6 KiB
Python
88 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from yuxi.channel.domain.model.plugin_registry.plugin_source import PluginOrigin
|
|
from yuxi.channel.infrastructure.plugin.discovery import PluginDiscovery
|
|
from yuxi.channel.infrastructure.plugin.manifest_parser import ManifestParser
|
|
|
|
|
|
class TestPluginDiscovery:
|
|
@pytest.fixture
|
|
def discovery(self):
|
|
return PluginDiscovery()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_discover_workspace_empty_dir(self, discovery):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
candidates = await discovery.discover_workspace(tmpdir)
|
|
assert candidates == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_discover_workspace_skips_no_manifest(self, discovery):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
Path(tmpdir, "plugin1").mkdir()
|
|
candidates = await discovery.discover_workspace(tmpdir)
|
|
assert candidates == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_discover_workspace_skips_hidden_dirs(self, discovery):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
Path(tmpdir, ".hidden").mkdir()
|
|
Path(tmpdir, "_private").mkdir()
|
|
candidates = await discovery.discover_workspace(tmpdir)
|
|
assert candidates == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_discover_workspace_finds_manifest(self, discovery):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
plugin_dir = Path(tmpdir, "my_plugin")
|
|
plugin_dir.mkdir()
|
|
manifest_path = plugin_dir / "yuxi.plugin.yaml"
|
|
manifest_path.write_text(
|
|
"id: my_plugin\nname: My Plugin\nversion: 1.0.0\nchannel_type: test\nentry: adapter.py\n"
|
|
)
|
|
|
|
candidates = await discovery.discover_workspace(tmpdir)
|
|
assert len(candidates) == 1
|
|
assert candidates[0].plugin_id == "my_plugin"
|
|
assert candidates[0].source.origin == PluginOrigin.WORKSPACE
|
|
assert candidates[0].source.entry_file.endswith("adapter.py")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_discover_all_combines_sources(self, discovery):
|
|
with tempfile.TemporaryDirectory() as ws_dir, tempfile.TemporaryDirectory() as inst_dir:
|
|
for d, name in [(ws_dir, "ws_plugin"), (inst_dir, "inst_plugin")]:
|
|
plugin_dir = Path(d, name)
|
|
plugin_dir.mkdir()
|
|
manifest_path = plugin_dir / "yuxi.plugin.yaml"
|
|
manifest_path.write_text(f"id: {name}\nname: {name.capitalize()}\nversion: 1.0.0\nchannel_type: test\n")
|
|
|
|
discovery_ws = PluginDiscovery()
|
|
candidates_ws = await discovery_ws.discover_workspace(ws_dir)
|
|
candidates_inst = await discovery_ws.discover_installed(inst_dir)
|
|
assert len(candidates_ws) == 1
|
|
assert len(candidates_inst) == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_find_existing_plugin(self, discovery):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
plugin_dir = Path(tmpdir, "target")
|
|
plugin_dir.mkdir()
|
|
manifest_path = plugin_dir / "yuxi.plugin.yaml"
|
|
manifest_path.write_text("id: target\nname: Target\nversion: 1.0.0\nchannel_type: test\n")
|
|
|
|
result = await discovery.find("target")
|
|
assert result is None
|
|
|
|
result = await PluginDiscovery().discover_workspace(tmpdir)
|
|
assert len(result) == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_find_nonexistent_plugin(self, discovery):
|
|
result = await discovery.find("nonexistent")
|
|
assert result is None
|