mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
refactor: unify service configuration boundary
This commit is contained in:
+11
-16
@@ -13,8 +13,8 @@
|
||||
"runtime_to_db": [],
|
||||
"workflow_to_db": []
|
||||
},
|
||||
"edge_count": 6544,
|
||||
"edge_sha256": "562471ed16680ca403b6cdb7661b20b1200c53b75da5e162efe4e7400cde174b",
|
||||
"edge_count": 6539,
|
||||
"edge_sha256": "28c7ab8544ede290e6dacf5f126ca1aad95e09627d874a0d23c6476108a88e1b",
|
||||
"edges": [
|
||||
"app -> app.runtime",
|
||||
"app -> app.runtime.compat",
|
||||
@@ -202,9 +202,9 @@
|
||||
"app.agent.llm.capability -> app.agent",
|
||||
"app.agent.llm.capability -> app.agent.llm",
|
||||
"app.agent.llm.capability -> app.agent.llm.helper",
|
||||
"app.agent.llm.capability -> app.application",
|
||||
"app.agent.llm.capability -> app.application.notification",
|
||||
"app.agent.llm.capability -> app.runtime",
|
||||
"app.agent.llm.capability -> app.runtime.extensions",
|
||||
"app.agent.llm.capability -> app.runtime.extensions.service_config",
|
||||
"app.agent.llm.capability -> app.runtime.log",
|
||||
"app.agent.llm.capability -> app.runtime.settings",
|
||||
"app.agent.llm.capability -> app.schemas",
|
||||
@@ -432,10 +432,9 @@
|
||||
"app.agent.tools.base -> app.application",
|
||||
"app.agent.tools.base -> app.application.messaging",
|
||||
"app.agent.tools.base -> app.application.messaging.agent",
|
||||
"app.agent.tools.base -> app.application.notification",
|
||||
"app.agent.tools.base -> app.chain",
|
||||
"app.agent.tools.base -> app.runtime",
|
||||
"app.agent.tools.base -> app.runtime.extensions",
|
||||
"app.agent.tools.base -> app.runtime.extensions.service_config",
|
||||
"app.agent.tools.base -> app.runtime.log",
|
||||
"app.agent.tools.base -> app.runtime.settings",
|
||||
"app.agent.tools.base -> app.schemas",
|
||||
@@ -952,11 +951,11 @@
|
||||
"app.agent.tools.impl.query_library_latest -> app.agent.tools",
|
||||
"app.agent.tools.impl.query_library_latest -> app.agent.tools.base",
|
||||
"app.agent.tools.impl.query_library_latest -> app.agent.tools.tags",
|
||||
"app.agent.tools.impl.query_library_latest -> app.application",
|
||||
"app.agent.tools.impl.query_library_latest -> app.application.mediaserver",
|
||||
"app.agent.tools.impl.query_library_latest -> app.chain",
|
||||
"app.agent.tools.impl.query_library_latest -> app.chain.mediaserver",
|
||||
"app.agent.tools.impl.query_library_latest -> app.runtime",
|
||||
"app.agent.tools.impl.query_library_latest -> app.runtime.extensions",
|
||||
"app.agent.tools.impl.query_library_latest -> app.runtime.extensions.service_config",
|
||||
"app.agent.tools.impl.query_library_latest -> app.runtime.log",
|
||||
"app.agent.tools.impl.query_market_plugins -> app.agent",
|
||||
"app.agent.tools.impl.query_market_plugins -> app.agent.tools",
|
||||
@@ -1983,12 +1982,11 @@
|
||||
"app.api.endpoints.message -> app.application.configuration",
|
||||
"app.api.endpoints.message -> app.application.messaging",
|
||||
"app.api.endpoints.message -> app.application.messaging.message",
|
||||
"app.api.endpoints.message -> app.application.notification",
|
||||
"app.api.endpoints.message -> app.chain",
|
||||
"app.api.endpoints.message -> app.chain.message",
|
||||
"app.api.endpoints.message -> app.runtime",
|
||||
"app.api.endpoints.message -> app.runtime.config",
|
||||
"app.api.endpoints.message -> app.runtime.extensions",
|
||||
"app.api.endpoints.message -> app.runtime.extensions.service_config",
|
||||
"app.api.endpoints.message -> app.runtime.log",
|
||||
"app.api.endpoints.message -> app.runtime.tasks",
|
||||
"app.api.endpoints.message -> app.schemas",
|
||||
@@ -2941,6 +2939,7 @@
|
||||
"app.chain._messaging -> app.application.messaging",
|
||||
"app.chain._messaging -> app.application.messaging.agent",
|
||||
"app.chain._messaging -> app.application.messaging.message",
|
||||
"app.chain._messaging -> app.application.notification",
|
||||
"app.chain._messaging -> app.domain",
|
||||
"app.chain._messaging -> app.domain.context",
|
||||
"app.chain._messaging -> app.domain.meta",
|
||||
@@ -2948,8 +2947,6 @@
|
||||
"app.chain._messaging -> app.foundation",
|
||||
"app.chain._messaging -> app.foundation.identity",
|
||||
"app.chain._messaging -> app.runtime",
|
||||
"app.chain._messaging -> app.runtime.extensions",
|
||||
"app.chain._messaging -> app.runtime.extensions.service_config",
|
||||
"app.chain._messaging -> app.runtime.log",
|
||||
"app.chain._messaging -> app.schemas",
|
||||
"app.chain._messaging -> app.schemas.message",
|
||||
@@ -3166,13 +3163,12 @@
|
||||
"app.chain.mediaserver -> app.application",
|
||||
"app.chain.mediaserver -> app.application.chain",
|
||||
"app.chain.mediaserver -> app.application.chain.data",
|
||||
"app.chain.mediaserver -> app.application.mediaserver",
|
||||
"app.chain.mediaserver -> app.application.security",
|
||||
"app.chain.mediaserver -> app.application.security.url",
|
||||
"app.chain.mediaserver -> app.chain",
|
||||
"app.chain.mediaserver -> app.runtime",
|
||||
"app.chain.mediaserver -> app.runtime.config",
|
||||
"app.chain.mediaserver -> app.runtime.extensions",
|
||||
"app.chain.mediaserver -> app.runtime.extensions.service_config",
|
||||
"app.chain.mediaserver -> app.runtime.log",
|
||||
"app.chain.mediaserver -> app.schemas",
|
||||
"app.chain.mediaserver -> app.schemas.mediaserver",
|
||||
@@ -5801,6 +5797,7 @@
|
||||
"app.scheduler -> app.application.configuration",
|
||||
"app.scheduler -> app.application.database",
|
||||
"app.scheduler -> app.application.image",
|
||||
"app.scheduler -> app.application.mediaserver",
|
||||
"app.scheduler -> app.application.messaging",
|
||||
"app.scheduler -> app.application.messaging.message",
|
||||
"app.scheduler -> app.application.outbox",
|
||||
@@ -5824,8 +5821,6 @@
|
||||
"app.scheduler -> app.runtime.config",
|
||||
"app.scheduler -> app.runtime.correlation",
|
||||
"app.scheduler -> app.runtime.events",
|
||||
"app.scheduler -> app.runtime.extensions",
|
||||
"app.scheduler -> app.runtime.extensions.service_config",
|
||||
"app.scheduler -> app.runtime.gc",
|
||||
"app.scheduler -> app.runtime.log",
|
||||
"app.scheduler -> app.runtime.observability",
|
||||
|
||||
@@ -199,7 +199,7 @@ class AgentCapabilityManagerTest(unittest.TestCase):
|
||||
]
|
||||
|
||||
with patch(
|
||||
"app.runtime.extensions.service_config.ServiceConfigHelper.get_notification_configs",
|
||||
"app.agent.llm.capability.get_notification_configs",
|
||||
return_value=configs,
|
||||
):
|
||||
self.assertTrue(
|
||||
|
||||
@@ -454,7 +454,7 @@ def test_channel_primary_id_defaults_to_admin_without_admin_list():
|
||||
)
|
||||
|
||||
with patch(
|
||||
"app.agent.tools.base.ServiceConfigHelper.get_notification_configs",
|
||||
"app.agent.tools.base.get_notification_configs",
|
||||
return_value=[
|
||||
SimpleNamespace(
|
||||
name="telegram-main",
|
||||
@@ -477,7 +477,7 @@ def test_channel_primary_id_mismatch_remains_non_admin():
|
||||
)
|
||||
|
||||
with patch(
|
||||
"app.agent.tools.base.ServiceConfigHelper.get_notification_configs",
|
||||
"app.agent.tools.base.get_notification_configs",
|
||||
return_value=[
|
||||
SimpleNamespace(
|
||||
name="telegram-main",
|
||||
@@ -500,7 +500,7 @@ def test_feishu_primary_open_id_defaults_to_admin():
|
||||
)
|
||||
|
||||
with patch(
|
||||
"app.agent.tools.base.ServiceConfigHelper.get_notification_configs",
|
||||
"app.agent.tools.base.get_notification_configs",
|
||||
return_value=[
|
||||
SimpleNamespace(
|
||||
name="feishu-main",
|
||||
@@ -523,7 +523,7 @@ def test_channel_primary_id_still_prefers_admin_list():
|
||||
)
|
||||
|
||||
with patch(
|
||||
"app.agent.tools.base.ServiceConfigHelper.get_notification_configs",
|
||||
"app.agent.tools.base.get_notification_configs",
|
||||
return_value=[
|
||||
SimpleNamespace(
|
||||
name="telegram-main",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"""应用层服务配置目录测试。"""
|
||||
|
||||
from app.application import service
|
||||
from app.application.mediaserver import get_mediaserver_configs
|
||||
from app.application.notification import (
|
||||
get_notification_configs,
|
||||
get_notification_switch,
|
||||
)
|
||||
from app.schemas.system import (
|
||||
MediaServerConf,
|
||||
NotificationConf,
|
||||
NotificationSwitchConf,
|
||||
)
|
||||
from app.schemas.types import MessageType, SystemConfigKey
|
||||
|
||||
|
||||
def test_named_service_config_helpers_preserve_enabled_policy(monkeypatch) -> None:
|
||||
"""命名应用函数应复用同一配置目录,并显式控制禁用项可见性。"""
|
||||
configs = {
|
||||
SystemConfigKey.MediaServers: [
|
||||
MediaServerConf(name="enabled-media", type="plex", enabled=True),
|
||||
MediaServerConf(name="disabled-media", type="emby", enabled=False),
|
||||
],
|
||||
SystemConfigKey.Notifications: [
|
||||
NotificationConf(name="enabled-channel", type="telegram", enabled=True),
|
||||
NotificationConf(name="disabled-channel", type="wechat", enabled=False),
|
||||
],
|
||||
SystemConfigKey.NotificationSwitchs: [
|
||||
NotificationSwitchConf(
|
||||
type=MessageType.Download.value,
|
||||
action="admin",
|
||||
)
|
||||
],
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
service,
|
||||
"_config_loader",
|
||||
lambda config_key, _conf_type: configs.get(config_key, []),
|
||||
)
|
||||
|
||||
assert [item.name for item in get_mediaserver_configs()] == ["enabled-media"]
|
||||
assert [
|
||||
item.name for item in get_mediaserver_configs(include_disabled=True)
|
||||
] == ["enabled-media", "disabled-media"]
|
||||
assert [item.name for item in get_notification_configs()] == ["enabled-channel"]
|
||||
assert [
|
||||
item.name for item in get_notification_configs(include_disabled=True)
|
||||
] == ["enabled-channel", "disabled-channel"]
|
||||
assert get_notification_switch(MessageType.Download) == "admin"
|
||||
@@ -614,6 +614,32 @@ def test_monitor_dispatcher_uses_explicit_history_port_getter():
|
||||
assert violations == []
|
||||
|
||||
|
||||
def test_canonical_service_config_consumers_use_application_directory():
|
||||
"""Chain、API、Scheduler 与 Agent 不得绕过应用目录读取运行时配置 Helper。"""
|
||||
paths = [
|
||||
APP_ROOT / "chain" / "_messaging.py",
|
||||
APP_ROOT / "chain" / "mediaserver.py",
|
||||
APP_ROOT / "api" / "endpoints" / "message.py",
|
||||
APP_ROOT / "scheduler.py",
|
||||
APP_ROOT / "agent" / "llm" / "capability.py",
|
||||
APP_ROOT / "agent" / "tools" / "base.py",
|
||||
APP_ROOT / "agent" / "tools" / "impl" / "query_library_latest.py",
|
||||
]
|
||||
violations: list[str] = []
|
||||
for path in paths:
|
||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path))
|
||||
for node in ast.walk(tree):
|
||||
if (
|
||||
isinstance(node, ast.ImportFrom)
|
||||
and node.module == "app.runtime.extensions.service_config"
|
||||
):
|
||||
violations.append(
|
||||
f"{path.relative_to(PROJECT_ROOT).as_posix()}:{node.lineno}"
|
||||
)
|
||||
|
||||
assert violations == []
|
||||
|
||||
|
||||
def test_plugin_components_do_not_reexport_legacy_abi_names():
|
||||
"""新插件组件只提供 canonical 能力,不得复制旧 Helper、Manager 或 Oper 导出。"""
|
||||
violations: list[str] = []
|
||||
|
||||
@@ -106,7 +106,7 @@ def test_sync_persists_music_without_querying_tv_episodes(database):
|
||||
"get_chain_media_server_port",
|
||||
lambda: MediaServerOper(session),
|
||||
), patch.object(
|
||||
MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
|
||||
MEDIA_SERVER_CHAIN_MODULE,
|
||||
"get_mediaserver_configs",
|
||||
return_value=[SimpleNamespace(name="navidrome", enabled=True, sync_libraries=["all"])],
|
||||
):
|
||||
@@ -199,7 +199,7 @@ def test_sync_updates_rows_and_removes_stale_entries(database):
|
||||
"get_chain_media_server_port",
|
||||
lambda: MediaServerOper(session),
|
||||
), patch.object(
|
||||
MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
|
||||
MEDIA_SERVER_CHAIN_MODULE,
|
||||
"get_mediaserver_configs",
|
||||
return_value=[SimpleNamespace(name="plex", enabled=True, sync_libraries=["movies"])],
|
||||
):
|
||||
@@ -283,7 +283,7 @@ def test_sync_queries_counts_before_items_and_reports_media_progress(database):
|
||||
"get_chain_media_server_port",
|
||||
lambda: MediaServerOper(session),
|
||||
), patch.object(
|
||||
MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
|
||||
MEDIA_SERVER_CHAIN_MODULE,
|
||||
"get_mediaserver_configs",
|
||||
return_value=[
|
||||
SimpleNamespace(name="plex-a", enabled=True, sync_libraries=["all"]),
|
||||
@@ -340,9 +340,9 @@ def test_sync_targets_one_server_without_excluding_other_enabled_servers(monkeyp
|
||||
FakeMediaServerOper,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
|
||||
MEDIA_SERVER_CHAIN_MODULE,
|
||||
"get_mediaserver_configs",
|
||||
lambda: [
|
||||
lambda **_kwargs: [
|
||||
SimpleNamespace(name="plex-a", enabled=True, sync_libraries=["all"]),
|
||||
SimpleNamespace(name="plex-b", enabled=True, sync_libraries=["all"]),
|
||||
],
|
||||
@@ -383,9 +383,9 @@ def test_sync_stops_without_emitting_completion_after_stop_signal(monkeypatch):
|
||||
)
|
||||
monkeypatch.setattr(chain, "_sync_server_libraries", stop_during_sync)
|
||||
monkeypatch.setattr(
|
||||
MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
|
||||
MEDIA_SERVER_CHAIN_MODULE,
|
||||
"get_mediaserver_configs",
|
||||
lambda: [server],
|
||||
lambda **_kwargs: [server],
|
||||
)
|
||||
global_vars.STOP_EVENT.clear()
|
||||
try:
|
||||
|
||||
@@ -90,9 +90,9 @@ def test_clear_cache_is_manual_only(monkeypatch):
|
||||
]:
|
||||
monkeypatch.setattr(scheduler_module, name, lambda: generic_chain)
|
||||
monkeypatch.setattr(
|
||||
scheduler_module.ServiceConfigHelper,
|
||||
scheduler_module,
|
||||
"get_mediaserver_configs",
|
||||
lambda: [],
|
||||
lambda **_kwargs: [],
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
scheduler_module,
|
||||
|
||||
Reference in New Issue
Block a user