86 lines
2.5 KiB
Python
86 lines
2.5 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import sys
|
||
|
|
import tempfile
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from yuxi.channel.infrastructure.plugin.loader import PluginLoader
|
||
|
|
|
||
|
|
|
||
|
|
class FakeAdapterClass:
|
||
|
|
def __init__(self, **kwargs) -> None:
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def open(self) -> None:
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def close(self) -> None:
|
||
|
|
pass
|
||
|
|
|
||
|
|
@property
|
||
|
|
def capabilities(self):
|
||
|
|
return None
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def get_default_config(cls) -> dict:
|
||
|
|
return {}
|
||
|
|
|
||
|
|
|
||
|
|
class TestPluginLoader:
|
||
|
|
@pytest.fixture
|
||
|
|
def loader(self):
|
||
|
|
return PluginLoader()
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_load_module_by_path(self, loader):
|
||
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
||
|
|
f.write("class MyAdapter:\n")
|
||
|
|
f.write(" def __init__(self, **kwargs): pass\n")
|
||
|
|
f.write(" async def open(self): pass\n")
|
||
|
|
f.write(" async def close(self): pass\n")
|
||
|
|
f.write(" @property\n")
|
||
|
|
f.write(" def capabilities(self): return None\n")
|
||
|
|
f.write(" @classmethod\n")
|
||
|
|
f.write(" def get_default_config(cls): return {}\n")
|
||
|
|
path = f.name
|
||
|
|
|
||
|
|
try:
|
||
|
|
module = await loader.load(path)
|
||
|
|
assert module is not None
|
||
|
|
assert hasattr(module, "MyAdapter")
|
||
|
|
finally:
|
||
|
|
Path(path).unlink()
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_load_existing_module(self, loader):
|
||
|
|
module = await loader.load("sys")
|
||
|
|
assert module is sys
|
||
|
|
|
||
|
|
def test_extract_adapter_class_found(self, loader):
|
||
|
|
module = type("Module", (), {"MyAdapter": FakeAdapterClass})()
|
||
|
|
result = loader.extract_adapter_class(module)
|
||
|
|
assert result is FakeAdapterClass
|
||
|
|
|
||
|
|
def test_extract_adapter_class_none(self, loader):
|
||
|
|
module = type("Module", (), {"foo": "bar"})()
|
||
|
|
result = loader.extract_adapter_class(module)
|
||
|
|
assert result is None
|
||
|
|
|
||
|
|
def test_extract_adapter_class_skips_private(self, loader):
|
||
|
|
class _PrivateAdapter(FakeAdapterClass):
|
||
|
|
pass
|
||
|
|
|
||
|
|
module = type("Module", (), {"_PrivateAdapter": _PrivateAdapter})()
|
||
|
|
result = loader.extract_adapter_class(module)
|
||
|
|
assert result is None
|
||
|
|
|
||
|
|
def test_extract_adapter_class_requires_methods(self, loader):
|
||
|
|
class Incomplete:
|
||
|
|
pass
|
||
|
|
|
||
|
|
module = type("Module", (), {"Incomplete": Incomplete})()
|
||
|
|
result = loader.extract_adapter_class(module)
|
||
|
|
assert result is None
|