mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-04 14:24:58 +08:00
refactor(startup): isolate server service composition
This commit is contained in:
50
app/startup/composition/server.py
Normal file
50
app/startup/composition/server.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""MoviePilot Server 应用服务的宿主组合根。"""
|
||||
|
||||
from app.adapters.external.server import (
|
||||
MoviePilotServerHelper,
|
||||
configure_server_application_services,
|
||||
)
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.server.report import ServerReportService
|
||||
from app.application.server.share import ServerSharingService
|
||||
from app.application.subscription.contract import SubscriptionRepository
|
||||
from app.application.workflow import WorkflowQueryService
|
||||
from app.schemas.types import SystemConfigKey
|
||||
|
||||
|
||||
def configure_server_services(
|
||||
workflow_query: WorkflowQueryService,
|
||||
subscription_repository: SubscriptionRepository,
|
||||
) -> None:
|
||||
"""构造并登记中心服务上报与分享用例,构造阶段不执行外部请求。"""
|
||||
configure_server_application_services(
|
||||
report_service=ServerReportService(
|
||||
config_reader=lambda key: get_configured_system_config().get(key),
|
||||
config_writer=lambda key, value: get_configured_system_config().set(key, value),
|
||||
async_config_writer=lambda key, value: get_configured_system_config().async_set(key, value),
|
||||
installed_plugins_provider=lambda: (
|
||||
get_configured_system_config().get(SystemConfigKey.UserInstalledPlugins) or []
|
||||
),
|
||||
subscribes_provider=subscription_repository.list,
|
||||
async_subscribes_provider=subscription_repository.async_list,
|
||||
plugin_report_sender=MoviePilotServerHelper.plugin_install_report,
|
||||
async_plugin_report_sender=MoviePilotServerHelper.async_plugin_install_report,
|
||||
subscribe_report_sender=MoviePilotServerHelper.subscribe_report,
|
||||
async_subscribe_report_sender=MoviePilotServerHelper.async_subscribe_report,
|
||||
repo_url_sanitizer=MoviePilotServerHelper.sanitize_plugin_repo_url,
|
||||
),
|
||||
sharing_service=ServerSharingService(
|
||||
subscribe_provider=subscription_repository.get,
|
||||
async_subscribe_provider=subscription_repository.async_get,
|
||||
workflow_provider=workflow_query.get_sync,
|
||||
async_workflow_provider=workflow_query.get,
|
||||
user_uuid_provider=MoviePilotServerHelper.get_user_uuid,
|
||||
subscribe_sender=MoviePilotServerHelper.subscribe_share,
|
||||
async_subscribe_sender=MoviePilotServerHelper.async_subscribe_share,
|
||||
workflow_sender=MoviePilotServerHelper.workflow_share,
|
||||
async_workflow_sender=MoviePilotServerHelper.async_workflow_share,
|
||||
response_handler=MoviePilotServerHelper._handle_response,
|
||||
subscribe_cache_clearer=MoviePilotServerHelper._clear_subscribe_share_cache,
|
||||
workflow_cache_clearer=MoviePilotServerHelper._clear_workflow_share_cache,
|
||||
),
|
||||
)
|
||||
@@ -18,10 +18,7 @@ except ImportError as e:
|
||||
print(error_message, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
from app.adapters.external.server import (
|
||||
MoviePilotServerHelper,
|
||||
configure_server_application_services,
|
||||
)
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.adapters.network.doh import DohHelper
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.adapters.system.resource import (
|
||||
@@ -64,16 +61,10 @@ from app.application.messaging.message import (
|
||||
from app.application.module import configure_module_runtime
|
||||
from app.application.outbox import configure_outbox_dispatcher
|
||||
from app.application.security.url import close_image_proxy_block_log_coalescer
|
||||
from app.application.server.report import ServerReportService
|
||||
from app.application.server.share import ServerSharingService
|
||||
from app.application.service import configure_service_directory
|
||||
from app.application.site.health import SiteHealthService, configure_site_health_service
|
||||
from app.application.site.query import SiteQueryService, configure_site_query_service
|
||||
from app.application.subscription.contract import SubscriptionRepository
|
||||
from app.application.workflow import (
|
||||
WorkflowQueryService,
|
||||
configure_workflow_execution,
|
||||
)
|
||||
from app.application.workflow import configure_workflow_execution
|
||||
from app.command import CommandChain
|
||||
from app.db.adapters.agent import (
|
||||
SessionAgentTaskRepository,
|
||||
@@ -178,6 +169,7 @@ from app.startup.composition.security import (
|
||||
configure_security_access,
|
||||
configure_security_services,
|
||||
)
|
||||
from app.startup.composition.server import configure_server_services
|
||||
from app.startup.composition.subscription import (
|
||||
async_rule_group_mutation_scope,
|
||||
build_subscription_batch_writer,
|
||||
@@ -254,48 +246,14 @@ def _build_chain_runtime_context(
|
||||
)
|
||||
|
||||
|
||||
def configure_runtime_data_providers(
|
||||
workflow_query: WorkflowQueryService,
|
||||
subscription_repository: SubscriptionRepository,
|
||||
) -> None:
|
||||
"""在启动组合层装配运行时和外部服务所需的数据库读取能力。"""
|
||||
def configure_runtime_data_providers() -> None:
|
||||
"""在启动组合层装配模块和配置目录的运行时读取能力。"""
|
||||
configure_service_config_reader(lambda key: get_configured_system_config().get(key))
|
||||
configure_module_runtime(lambda: ModuleManager())
|
||||
configure_service_directory(
|
||||
configs=ServiceConfigHelper.get_configs,
|
||||
modules=lambda module_type: ModuleManager().get_running_type_modules(module_type),
|
||||
)
|
||||
configure_server_application_services(
|
||||
report_service=ServerReportService(
|
||||
config_reader=lambda key: get_configured_system_config().get(key),
|
||||
config_writer=lambda key, value: get_configured_system_config().set(key, value),
|
||||
async_config_writer=lambda key, value: get_configured_system_config().async_set(key, value),
|
||||
installed_plugins_provider=lambda: (
|
||||
get_configured_system_config().get(SystemConfigKey.UserInstalledPlugins) or []
|
||||
),
|
||||
subscribes_provider=subscription_repository.list,
|
||||
async_subscribes_provider=subscription_repository.async_list,
|
||||
plugin_report_sender=MoviePilotServerHelper.plugin_install_report,
|
||||
async_plugin_report_sender=(MoviePilotServerHelper.async_plugin_install_report),
|
||||
subscribe_report_sender=MoviePilotServerHelper.subscribe_report,
|
||||
async_subscribe_report_sender=MoviePilotServerHelper.async_subscribe_report,
|
||||
repo_url_sanitizer=MoviePilotServerHelper.sanitize_plugin_repo_url,
|
||||
),
|
||||
sharing_service=ServerSharingService(
|
||||
subscribe_provider=subscription_repository.get,
|
||||
async_subscribe_provider=subscription_repository.async_get,
|
||||
workflow_provider=workflow_query.get_sync,
|
||||
async_workflow_provider=workflow_query.get,
|
||||
user_uuid_provider=MoviePilotServerHelper.get_user_uuid,
|
||||
subscribe_sender=MoviePilotServerHelper.subscribe_share,
|
||||
async_subscribe_sender=MoviePilotServerHelper.async_subscribe_share,
|
||||
workflow_sender=MoviePilotServerHelper.workflow_share,
|
||||
async_workflow_sender=MoviePilotServerHelper.async_workflow_share,
|
||||
response_handler=MoviePilotServerHelper._handle_response,
|
||||
subscribe_cache_clearer=(MoviePilotServerHelper._clear_subscribe_share_cache),
|
||||
workflow_cache_clearer=(MoviePilotServerHelper._clear_workflow_share_cache),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def configure_wallpaper_services() -> None:
|
||||
@@ -714,7 +672,8 @@ async def _initialize_modules() -> HostRuntime:
|
||||
tasks=get_task_registry(),
|
||||
)
|
||||
publish_database_services(database_services)
|
||||
configure_runtime_data_providers(workflow_query, subscription_repository)
|
||||
configure_runtime_data_providers()
|
||||
configure_server_services(workflow_query, subscription_repository)
|
||||
workflow_execution = TransactionalWorkflowExecutionService(SessionFactory)
|
||||
configure_workflow_execution(workflow_execution)
|
||||
configure_outbox_dispatcher(build_outbox_dispatcher)
|
||||
|
||||
@@ -70,7 +70,7 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
|
||||
|
||||
| 指标 | 当前值 | 解释 |
|
||||
|---|---:|---|
|
||||
| 宿主 Python 模块 / 内部依赖边 | 968 / 8,248 | `dependency-baseline.json` 当前快照 |
|
||||
| 宿主 Python 模块 / 内部依赖边 | 969 / 8,257 | `dependency-baseline.json` 当前快照 |
|
||||
| 非平凡 SCC | 1 | 仅保留精确 containment 的 29 模块 TMDB 移植包环 |
|
||||
| 跨层 DB 边界债务 | 0 | Application、Chain、API、Agent、Runtime、Workflow 到 DB 的受控债务均为零 |
|
||||
| Model/Oper 事务债务 | 0 | 自建 Session、自动事务装饰器、直接 commit/rollback 等基线均为零 |
|
||||
@@ -78,7 +78,7 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
|
||||
| Event Contract | 53 | 均已有 payload model,但当前全部是 diagnostic enforcement |
|
||||
| Python 源码量 | 约 298,700 行 | 60 个文件超过 1,000 行,10 个超过 2,000 行 |
|
||||
| 长方法 | 299 个超过 80 行 | 70 个超过 150 行,23 个超过 250 行;大量是私有方法 |
|
||||
| 全量 mypy 历史债务 | 10,347 / 593 文件 | strict frontier 当前覆盖 41 个文件,Domain projection 与既有 owner 拆分的受益调用方低水位已固化 |
|
||||
| 全量 mypy 历史债务 | 10,336 / 593 文件 | strict frontier 当前覆盖 41 个文件,Domain projection 与既有 owner 拆分的受益调用方低水位已固化 |
|
||||
| Ruff 历史诊断 | 648 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` |
|
||||
| 覆盖率低水位 | Application 81.00%,Domain 80.67% | Chain、Runtime、Agent、Adapter、Startup 未进入包级覆盖率门禁 |
|
||||
|
||||
|
||||
@@ -745,8 +745,8 @@ flowchart LR
|
||||
|
||||
| 指标 | 当前值 |
|
||||
|---|---:|
|
||||
| Python 模块 | 968 |
|
||||
| 内部导入边 | 8,248 |
|
||||
| Python 模块 | 969 |
|
||||
| 内部导入边 | 8,257 |
|
||||
| 非平凡 SCC | 1(精确 containment 的 TMDB 移植包环) |
|
||||
| Application / Chain 具体 Adapter 直连 | 0 / 0 |
|
||||
| Direct egress | 53(债务已清零,53 条精确 containment) |
|
||||
|
||||
@@ -152,6 +152,10 @@ startup order and must not recreate their concrete construction.
|
||||
owns durable handler validation, lazy event/notification replay binding and the
|
||||
short-transaction dispatcher factory. Initializers call this owner in
|
||||
startup order and must not recreate their concrete construction. Database runtime
|
||||
`startup/composition/server.py` owns MoviePilot Server report/share service
|
||||
construction and registration, including lazy local-data readers and external
|
||||
transport callbacks; `initializers/modules.py` retains only its startup-order call
|
||||
and the later explicit network warmup. Database runtime
|
||||
ownership rejects overlapping lifespans; after a successful worker shutdown, the
|
||||
configuration/database composition owners must revoke every provider they published
|
||||
before releasing database connections. Other domain providers remain owned by their
|
||||
|
||||
@@ -1089,8 +1089,8 @@
|
||||
"runtime_only": true
|
||||
}
|
||||
},
|
||||
"edge_count": 8248,
|
||||
"edge_sha256": "224c93fde383ba0fa2e5f5fba873b06a53bf9eaa17c28f18b285a5048dd071a8",
|
||||
"edge_count": 8257,
|
||||
"edge_sha256": "914b0e25fb7aaad53720fb92b6d9523344d7a909af7cd081a4669259253825d8",
|
||||
"edges": [
|
||||
"app -> app.foundation",
|
||||
"app -> app.foundation.environment",
|
||||
@@ -8754,6 +8754,19 @@
|
||||
"app.startup.composition.security -> app.startup.composition",
|
||||
"app.startup.composition.security -> app.startup.composition.context",
|
||||
"app.startup.composition.security -> app.startup.composition.database",
|
||||
"app.startup.composition.server -> app.adapters",
|
||||
"app.startup.composition.server -> app.adapters.external",
|
||||
"app.startup.composition.server -> app.adapters.external.server",
|
||||
"app.startup.composition.server -> app.application",
|
||||
"app.startup.composition.server -> app.application.configuration",
|
||||
"app.startup.composition.server -> app.application.server",
|
||||
"app.startup.composition.server -> app.application.server.report",
|
||||
"app.startup.composition.server -> app.application.server.share",
|
||||
"app.startup.composition.server -> app.application.subscription",
|
||||
"app.startup.composition.server -> app.application.subscription.contract",
|
||||
"app.startup.composition.server -> app.application.workflow",
|
||||
"app.startup.composition.server -> app.schemas",
|
||||
"app.startup.composition.server -> app.schemas.types",
|
||||
"app.startup.composition.subscription -> app.adapters",
|
||||
"app.startup.composition.subscription -> app.adapters.external",
|
||||
"app.startup.composition.subscription -> app.adapters.external.server",
|
||||
@@ -8898,15 +8911,10 @@
|
||||
"app.startup.initializers.modules -> app.application.outbox",
|
||||
"app.startup.initializers.modules -> app.application.security",
|
||||
"app.startup.initializers.modules -> app.application.security.url",
|
||||
"app.startup.initializers.modules -> app.application.server",
|
||||
"app.startup.initializers.modules -> app.application.server.report",
|
||||
"app.startup.initializers.modules -> app.application.server.share",
|
||||
"app.startup.initializers.modules -> app.application.service",
|
||||
"app.startup.initializers.modules -> app.application.site",
|
||||
"app.startup.initializers.modules -> app.application.site.health",
|
||||
"app.startup.initializers.modules -> app.application.site.query",
|
||||
"app.startup.initializers.modules -> app.application.subscription",
|
||||
"app.startup.initializers.modules -> app.application.subscription.contract",
|
||||
"app.startup.initializers.modules -> app.application.workflow",
|
||||
"app.startup.initializers.modules -> app.chain",
|
||||
"app.startup.initializers.modules -> app.chain.download",
|
||||
@@ -8975,6 +8983,7 @@
|
||||
"app.startup.initializers.modules -> app.startup.composition.network",
|
||||
"app.startup.initializers.modules -> app.startup.composition.outbox",
|
||||
"app.startup.initializers.modules -> app.startup.composition.security",
|
||||
"app.startup.initializers.modules -> app.startup.composition.server",
|
||||
"app.startup.initializers.modules -> app.startup.composition.subscription",
|
||||
"app.startup.initializers.modules -> app.startup.initializers",
|
||||
"app.startup.initializers.modules -> app.startup.initializers.agent",
|
||||
@@ -9341,7 +9350,7 @@
|
||||
"app.workflow.actions.transfer_file -> app.workflow",
|
||||
"app.workflow.actions.transfer_file -> app.workflow.actions"
|
||||
],
|
||||
"module_count": 968,
|
||||
"module_count": 969,
|
||||
"modules": [
|
||||
"app",
|
||||
"app.adapters",
|
||||
@@ -10270,6 +10279,7 @@
|
||||
"app.startup.composition.network",
|
||||
"app.startup.composition.outbox",
|
||||
"app.startup.composition.security",
|
||||
"app.startup.composition.server",
|
||||
"app.startup.composition.subscription",
|
||||
"app.startup.initializers",
|
||||
"app.startup.initializers.agent",
|
||||
|
||||
@@ -848,6 +848,46 @@ def test_modules_initializer_delegates_outbox_composition():
|
||||
assert "build_outbox_dispatcher" not in package_source
|
||||
|
||||
|
||||
def test_modules_initializer_delegates_server_service_composition():
|
||||
"""中心服务对象图只由单词型 composition owner 构造,initializer 仅保留调用顺序。"""
|
||||
initializer_source = (
|
||||
APP_ROOT / "startup" / "initializers" / "modules.py"
|
||||
).read_text(encoding="utf-8")
|
||||
composition_path = APP_ROOT / "startup" / "composition" / "server.py"
|
||||
composition_source = composition_path.read_text(encoding="utf-8")
|
||||
architecture_source = (
|
||||
PROJECT_ROOT / "docs" / "rules" / "05-architecture.md"
|
||||
).read_text(encoding="utf-8")
|
||||
package_tree = ast.parse(
|
||||
(APP_ROOT / "startup" / "composition" / "__init__.py").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
)
|
||||
|
||||
assert composition_path.is_file()
|
||||
assert not list(composition_path.parent.glob("server_*.py"))
|
||||
assert "from app.startup.composition.server import configure_server_services" in (
|
||||
initializer_source
|
||||
)
|
||||
assert "configure_server_services(workflow_query, subscription_repository)" in (
|
||||
initializer_source
|
||||
)
|
||||
assert "`startup/composition/server.py` owns MoviePilot Server" in (
|
||||
architecture_source
|
||||
)
|
||||
for detail in (
|
||||
"ServerReportService(",
|
||||
"ServerSharingService(",
|
||||
"configure_server_application_services(",
|
||||
):
|
||||
assert detail not in initializer_source
|
||||
assert detail in composition_source
|
||||
assert not any(
|
||||
isinstance(node, (ast.Import, ast.ImportFrom, ast.FunctionDef, ast.ClassDef))
|
||||
for node in package_tree.body
|
||||
)
|
||||
|
||||
|
||||
def test_canonical_workflow_oper_has_no_legacy_writer_or_duplicate_exports():
|
||||
"""工作流旧写入口只能存在于 SDK Legacy facade。"""
|
||||
oper_path = APP_ROOT / "db" / "oper" / "workflow.py"
|
||||
|
||||
153
tests/test_server_composition.py
Normal file
153
tests/test_server_composition.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""MoviePilot Server 生产组合根测试。"""
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
import app.startup.composition.server as server_composition
|
||||
from app.schemas.types import SystemConfigKey
|
||||
|
||||
|
||||
class _SystemConfig:
|
||||
"""记录中心服务组合回调触发的配置读写。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""初始化插件清单和读写调用记录。"""
|
||||
self.values = {SystemConfigKey.UserInstalledPlugins: ["TestPlugin"]}
|
||||
self.calls: list[tuple[str, object, object]] = []
|
||||
|
||||
def get(self, key):
|
||||
"""读取测试配置并记录访问。"""
|
||||
self.calls.append(("get", key, None))
|
||||
return self.values.get(key)
|
||||
|
||||
def set(self, key, value):
|
||||
"""同步写入测试配置并记录访问。"""
|
||||
self.calls.append(("set", key, value))
|
||||
self.values[key] = value
|
||||
return True
|
||||
|
||||
async def async_set(self, key, value):
|
||||
"""异步写入测试配置并记录访问。"""
|
||||
self.calls.append(("async_set", key, value))
|
||||
self.values[key] = value
|
||||
return True
|
||||
|
||||
|
||||
class _SubscriptionRepository:
|
||||
"""提供同步和异步订阅读取端口的测试仓储。"""
|
||||
|
||||
def list(self):
|
||||
"""返回同步订阅快照。"""
|
||||
return ["sync-list"]
|
||||
|
||||
async def async_list(self):
|
||||
"""返回异步订阅快照。"""
|
||||
return ["async-list"]
|
||||
|
||||
def get(self, subscribe_id):
|
||||
"""返回同步订阅标记。"""
|
||||
return ("sync-subscribe", subscribe_id)
|
||||
|
||||
async def async_get(self, subscribe_id):
|
||||
"""返回异步订阅标记。"""
|
||||
return ("async-subscribe", subscribe_id)
|
||||
|
||||
|
||||
class _WorkflowQuery:
|
||||
"""提供同步和异步工作流读取端口。"""
|
||||
|
||||
def get_sync(self, workflow_id):
|
||||
"""返回同步工作流标记。"""
|
||||
return ("sync-workflow", workflow_id)
|
||||
|
||||
async def get(self, workflow_id):
|
||||
"""返回异步工作流标记。"""
|
||||
return ("async-workflow", workflow_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_server_composition_wires_lazy_local_and_transport_ports(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
"""生产组合仅保存本地与传输回调,构造阶段不得读取配置或发起请求。"""
|
||||
configured = {}
|
||||
system_config = _SystemConfig()
|
||||
configuration_resolutions = []
|
||||
transport_calls = []
|
||||
|
||||
def get_system_config():
|
||||
"""记录配置服务只在回调执行时解析。"""
|
||||
configuration_resolutions.append(True)
|
||||
return system_config
|
||||
|
||||
def capture_services(*, report_service, sharing_service):
|
||||
"""捕获生产组合登记的两个应用服务。"""
|
||||
configured["report"] = report_service
|
||||
configured["sharing"] = sharing_service
|
||||
|
||||
def sync_transport(*args, **kwargs):
|
||||
"""记录同步中心服务传输调用。"""
|
||||
transport_calls.append(("sync", args, kwargs))
|
||||
return SimpleNamespace(status_code=200)
|
||||
|
||||
async def async_transport(*args, **kwargs):
|
||||
"""记录异步中心服务传输调用。"""
|
||||
transport_calls.append(("async", args, kwargs))
|
||||
return SimpleNamespace(status_code=200)
|
||||
|
||||
monkeypatch.setattr(
|
||||
server_composition,
|
||||
"get_configured_system_config",
|
||||
get_system_config,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
server_composition,
|
||||
"configure_server_application_services",
|
||||
capture_services,
|
||||
)
|
||||
helper = server_composition.MoviePilotServerHelper
|
||||
for name in (
|
||||
"plugin_install_report",
|
||||
"subscribe_report",
|
||||
"subscribe_share",
|
||||
"workflow_share",
|
||||
):
|
||||
monkeypatch.setattr(helper, name, sync_transport)
|
||||
for name in (
|
||||
"async_plugin_install_report",
|
||||
"async_subscribe_report",
|
||||
"async_subscribe_share",
|
||||
"async_workflow_share",
|
||||
):
|
||||
monkeypatch.setattr(helper, name, async_transport)
|
||||
monkeypatch.setattr(helper, "sanitize_plugin_repo_url", lambda value: value)
|
||||
monkeypatch.setattr(helper, "get_user_uuid", lambda: "user-uuid")
|
||||
monkeypatch.setattr(helper, "_handle_response", lambda _response, _clear: (True, "ok"))
|
||||
monkeypatch.setattr(helper, "_clear_subscribe_share_cache", lambda: None)
|
||||
monkeypatch.setattr(helper, "_clear_workflow_share_cache", lambda: None)
|
||||
|
||||
subscriptions = _SubscriptionRepository()
|
||||
workflows = _WorkflowQuery()
|
||||
server_composition.configure_server_services(workflows, subscriptions)
|
||||
|
||||
assert configuration_resolutions == []
|
||||
assert system_config.calls == []
|
||||
assert transport_calls == []
|
||||
|
||||
report = configured["report"]
|
||||
sharing = configured["sharing"]
|
||||
config_key = SystemConfigKey.UserInstalledPlugins
|
||||
assert report._config_reader(config_key) == ["TestPlugin"]
|
||||
assert report._config_writer(config_key, ["NextPlugin"]) is True
|
||||
assert await report._async_config_writer(config_key, ["AsyncPlugin"]) is True
|
||||
assert report._installed_plugins_provider() == ["AsyncPlugin"]
|
||||
assert report._subscribes_provider() == ["sync-list"]
|
||||
assert await report._async_subscribes_provider() == ["async-list"]
|
||||
assert sharing._subscribe_provider(7) == ("sync-subscribe", 7)
|
||||
assert await sharing._async_subscribe_provider(8) == ("async-subscribe", 8)
|
||||
assert sharing._workflow_provider(9) == ("sync-workflow", 9)
|
||||
assert await sharing._async_workflow_provider(10) == ("async-workflow", 10)
|
||||
assert sharing._user_uuid_provider() == "user-uuid"
|
||||
assert len(configuration_resolutions) == 4
|
||||
assert transport_calls == []
|
||||
Reference in New Issue
Block a user