Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/tests/plugins/core/deployment/conftest.py
T
insistence 4bd83bc7cd feat: 支持 v2 插件、独立前端和签名制品发布 (#130)
* feat: 支持 v2 插件、原生扩展与签名发布

* fix: 修复测试

* feat: 增强宿主 SDK、运行观测与制品维护能力

* feat: 增强文件传输、配置观测与本地开发能力

* feat: 支持 SSE 实时事件流并完善开发脚手架

* feat: 完善长连接鉴权复核与关闭回收

* feat: 增强生命周期与任务关闭可靠性

* feat: 收紧健康检查契约并补齐前端与真实网络验收

* feat: 增加SDK溯源与离线更新工具并验证跨版本兼容

* feat: 增加持久化业务示例与受控 Python 交付

* feat: 增加统一运行诊断与多worker状态观测

* feat: 增加固定目标发布等待与自动验收

* fix: 修复多环境插件部署前缀与代理配置不一致

* fix: 修复测试
2026-10-09 23:49:45 +08:00

132 lines
4.6 KiB
Python

import base64
import json
from collections.abc import AsyncIterator
from pathlib import Path
import pytest
import pytest_asyncio
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from config.database import Base
from module_admin.entity.do.job_do import SysJob
from module_admin.entity.do.menu_do import SysMenu
from plugins.core.artifacts import build_artifact
from plugins.core.deployment.config import PluginDeploymentConfig
from plugins.core.management.entity.do.models import (
SysPlugin,
SysPluginConfig,
SysPluginMenu,
SysPluginMigration,
SysPluginOperationLog,
)
from plugins.core.management.entity.do.release_models import SysPluginArtifact, SysPluginRelease, SysPluginWorker
from plugins.core.runtime.service.lifecycle_lock import NoopPluginLifecycleLock
@pytest.fixture(autouse=True)
def isolate_catalog_lifecycle_lock(monkeypatch: pytest.MonkeyPatch) -> None:
"""
为临时数据库测试显式替换导入锁,避免连接开发环境 Redis。
:param monkeypatch: pytest 替换工具
:return: None
"""
monkeypatch.setattr('plugins.core.deployment.catalog.RedisPluginLifecycleLock', NoopPluginLifecycleLock)
@pytest_asyncio.fixture
async def deployment_sessions(tmp_path: Path) -> AsyncIterator[async_sessionmaker[AsyncSession]]:
"""创建包含发布及必要宿主表的临时 SQLite 会话工厂。"""
engine = create_async_engine(f'sqlite+aiosqlite:///{tmp_path / "deployment.sqlite"}')
async with engine.begin() as connection:
await connection.run_sync(
Base.metadata.create_all,
tables=[
item.__table__
for item in (
SysPlugin,
SysPluginArtifact,
SysPluginRelease,
SysPluginWorker,
SysPluginConfig,
SysPluginMenu,
SysPluginMigration,
SysPluginOperationLog,
SysMenu,
SysJob,
)
],
)
try:
yield async_sessionmaker(engine, expire_on_commit=False)
finally:
await engine.dispose()
@pytest.fixture
def deployment_key() -> Ed25519PrivateKey:
"""生成发布测试使用的临时签名私钥。"""
return Ed25519PrivateKey.generate()
@pytest.fixture
def deployment_config(tmp_path: Path, deployment_key: Ed25519PrivateKey) -> PluginDeploymentConfig:
"""构造独立存储目录与受限发布者公钥配置。"""
backend = tmp_path / 'host'
(backend / 'plugins').mkdir(parents=True)
trust_file = tmp_path / 'trusted-publishers.json'
public_key = deployment_key.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
trust_file.write_text(
json.dumps(
{
'schemaVersion': 1,
'keys': [
{
'keyId': 'publisher',
'publicKey': base64.b64encode(public_key).decode(),
'pluginIds': ['artifact_demo'],
}
],
}
),
encoding='utf-8',
)
return PluginDeploymentConfig(backend, tmp_path / 'store', trust_file)
@pytest.fixture
def deployment_source(tmp_path: Path) -> Path:
"""创建用于验证静态维护边界的临时部署目录。"""
source = tmp_path / 'artifact_demo'
source.mkdir()
(source / 'plugin.yaml').write_text(
'manifestVersion: 2\n'
'id: artifact_demo\n'
'name: Signed deployment fixture\n'
'version: 1.0.0\n'
'backend:\n'
' module: plugins.artifact_demo\n'
' entrypoint: plugins.artifact_demo:create_plugin\n'
'frontend:\n'
' delivery:\n'
' type: none\n',
encoding='utf-8',
)
(source / '__init__.py').write_text(
'raise RuntimeError("Static release checks must never import plugin code")\n'
'def create_plugin(context):\n'
' raise RuntimeError("The injected maintenance runtime owns lifecycle execution")\n',
encoding='utf-8',
)
return source
@pytest.fixture
def deployment_archive(tmp_path: Path, deployment_source: Path, deployment_key: Ed25519PrivateKey) -> Path:
"""构建发布服务测试使用的签名制品。"""
path = tmp_path / 'deployment.rpk'
build_artifact(deployment_source, path, deployment_key, 'publisher')
return path