mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
feat(media): add native IMDb source module
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
"root": "app"
|
||||
},
|
||||
"settings_imports": {
|
||||
"count": 164,
|
||||
"count": 166,
|
||||
"files": [
|
||||
"app/adapters/cache/backends.py",
|
||||
"app/adapters/cache/redis.py",
|
||||
@@ -112,6 +112,8 @@
|
||||
"app/modules/filemanager/storages/smb.py",
|
||||
"app/modules/filemanager/storages/u115.py",
|
||||
"app/modules/filemanager/transhandler.py",
|
||||
"app/modules/imdb/__init__.py",
|
||||
"app/modules/imdb/api.py",
|
||||
"app/modules/indexer/parser/__init__.py",
|
||||
"app/modules/indexer/parser/rousi.py",
|
||||
"app/modules/indexer/spider/__init__.py",
|
||||
|
||||
+29
-3
@@ -13,8 +13,8 @@
|
||||
"runtime_to_db": [],
|
||||
"workflow_to_db": []
|
||||
},
|
||||
"edge_count": 6317,
|
||||
"edge_sha256": "11978842b2fd9bd6ccec86c01c5f1b9389d959bc53aab5e16e29e3c121b3dab9",
|
||||
"edge_count": 6341,
|
||||
"edge_sha256": "073824a6b2c8b3e9baf4755ce4dccf94693fb943d1baba0f621d3a84b17030c0",
|
||||
"edges": [
|
||||
"app -> app.runtime",
|
||||
"app -> app.runtime.compat",
|
||||
@@ -4213,6 +4213,30 @@
|
||||
"app.modules.filter -> app.runtime.log",
|
||||
"app.modules.filter -> app.schemas",
|
||||
"app.modules.filter -> app.schemas.types",
|
||||
"app.modules.imdb -> app.domain",
|
||||
"app.modules.imdb -> app.domain.context",
|
||||
"app.modules.imdb -> app.domain.media",
|
||||
"app.modules.imdb -> app.domain.meta",
|
||||
"app.modules.imdb -> app.domain.meta.metabase",
|
||||
"app.modules.imdb -> app.domain.scraper",
|
||||
"app.modules.imdb -> app.foundation",
|
||||
"app.modules.imdb -> app.foundation.text",
|
||||
"app.modules.imdb -> app.modules",
|
||||
"app.modules.imdb -> app.modules.imdb.api",
|
||||
"app.modules.imdb -> app.runtime",
|
||||
"app.modules.imdb -> app.runtime.config",
|
||||
"app.modules.imdb -> app.runtime.log",
|
||||
"app.modules.imdb -> app.schemas",
|
||||
"app.modules.imdb -> app.schemas.context",
|
||||
"app.modules.imdb -> app.schemas.media",
|
||||
"app.modules.imdb -> app.schemas.types",
|
||||
"app.modules.imdb.api -> app.adapters",
|
||||
"app.modules.imdb.api -> app.adapters.network",
|
||||
"app.modules.imdb.api -> app.adapters.network.http",
|
||||
"app.modules.imdb.api -> app.runtime",
|
||||
"app.modules.imdb.api -> app.runtime.cache",
|
||||
"app.modules.imdb.api -> app.runtime.config",
|
||||
"app.modules.imdb.api -> app.runtime.log",
|
||||
"app.modules.indexer -> app.application",
|
||||
"app.modules.indexer -> app.application.site",
|
||||
"app.modules.indexer -> app.application.site.health",
|
||||
@@ -6334,7 +6358,7 @@
|
||||
"app.workflow.actions.transfer_file -> app.workflow",
|
||||
"app.workflow.actions.transfer_file -> app.workflow.actions"
|
||||
],
|
||||
"module_count": 785,
|
||||
"module_count": 787,
|
||||
"modules": [
|
||||
"app",
|
||||
"app.adapters",
|
||||
@@ -6828,6 +6852,8 @@
|
||||
"app.modules.filemanager.storages.u115",
|
||||
"app.modules.filemanager.transhandler",
|
||||
"app.modules.filter",
|
||||
"app.modules.imdb",
|
||||
"app.modules.imdb.api",
|
||||
"app.modules.indexer",
|
||||
"app.modules.indexer.parser",
|
||||
"app.modules.indexer.parser.bitpt",
|
||||
|
||||
@@ -0,0 +1,464 @@
|
||||
"""IMDb 原生媒体数据源 Module 测试。"""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app.domain.context import MediaInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.modules.imdb import ImdbModule
|
||||
from app.modules.imdb.api import (
|
||||
ImdbAka,
|
||||
ImdbApi,
|
||||
ImdbCredit,
|
||||
ImdbDate,
|
||||
ImdbEpisode,
|
||||
ImdbImage,
|
||||
ImdbPerson,
|
||||
ImdbSeason,
|
||||
ImdbTitle,
|
||||
)
|
||||
from app.runtime.config import settings
|
||||
from app.schemas.types import MediaRecognizeType, MediaSource, MediaType
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def imdb_title() -> ImdbTitle:
|
||||
"""构造一条包含完整基础字段的 IMDb 电视剧条目。"""
|
||||
return ImdbTitle(
|
||||
id="tt0903747",
|
||||
type="tvSeries",
|
||||
primaryTitle="Breaking Bad",
|
||||
originalTitle="Breaking Bad",
|
||||
primaryImage={"url": "https://image.example/poster.jpg", "type": "poster"},
|
||||
startYear=2008,
|
||||
runtimeSeconds=2820,
|
||||
genres=["Crime", "Drama"],
|
||||
rating={"aggregateRating": 9.5, "voteCount": 2200000},
|
||||
plot="A chemistry teacher becomes a methamphetamine producer.",
|
||||
originCountries=[{"code": "US", "name": "United States"}],
|
||||
spokenLanguages=[{"code": "eng", "name": "English"}],
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def imdb_api(imdb_title: ImdbTitle) -> Mock:
|
||||
"""构造同步和异步方法行为一致的 IMDb API 替身。"""
|
||||
api = Mock(spec=ImdbApi)
|
||||
api.search_titles.return_value = [imdb_title]
|
||||
api.get_title.return_value = imdb_title
|
||||
api.list_akas.return_value = [ImdbAka(text="绝命毒师")]
|
||||
api.list_credits.return_value = [
|
||||
ImdbCredit(
|
||||
name=ImdbPerson(
|
||||
id="nm0533713",
|
||||
displayName="Vince Gilligan",
|
||||
primaryImage=ImdbImage(url="https://image.example/director.jpg"),
|
||||
),
|
||||
category="DIRECTOR",
|
||||
),
|
||||
ImdbCredit(
|
||||
name=ImdbPerson(
|
||||
id="nm0186505",
|
||||
displayName="Bryan Cranston",
|
||||
primaryImage=ImdbImage(url="https://image.example/actor.jpg"),
|
||||
),
|
||||
category="ACTOR",
|
||||
characters=["Walter White"],
|
||||
),
|
||||
]
|
||||
api.list_images.return_value = [
|
||||
ImdbImage(url="https://image.example/backdrop.jpg", type="still_frame")
|
||||
]
|
||||
api.list_episodes.return_value = [
|
||||
ImdbEpisode(
|
||||
id="tt0959621",
|
||||
season="1",
|
||||
episodeNumber=1,
|
||||
releaseDate=ImdbDate(year=2008, month=1, day=20),
|
||||
)
|
||||
]
|
||||
api.list_seasons.return_value = [ImdbSeason(season="1", episodeCount=7)]
|
||||
api.async_search_titles = AsyncMock(return_value=[imdb_title])
|
||||
api.async_get_title = AsyncMock(return_value=imdb_title)
|
||||
api.async_list_akas = AsyncMock(return_value=api.list_akas.return_value)
|
||||
api.async_list_credits = AsyncMock(return_value=api.list_credits.return_value)
|
||||
api.async_list_images = AsyncMock(return_value=api.list_images.return_value)
|
||||
api.async_list_episodes = AsyncMock(return_value=api.list_episodes.return_value)
|
||||
api.async_list_seasons = AsyncMock(return_value=api.list_seasons.return_value)
|
||||
return api
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def imdb_module(imdb_api: Mock) -> ImdbModule:
|
||||
"""构造注入离线 API 替身的 IMDb Module。"""
|
||||
module = ImdbModule()
|
||||
module.imdb_api = imdb_api
|
||||
module.scraper = Mock()
|
||||
return module
|
||||
|
||||
|
||||
def test_imdb_module_declares_independent_media_recognizer() -> None:
|
||||
"""IMDb 应作为独立宿主媒体识别 Module 暴露稳定元数据。"""
|
||||
assert ImdbModule.get_name() == "IMDb"
|
||||
assert ImdbModule.get_subtype() == MediaRecognizeType.IMDb
|
||||
assert ImdbModule.get_priority() == 4
|
||||
|
||||
|
||||
def test_imdb_api_search_uses_keyless_imdb_suggestion_endpoint() -> None:
|
||||
"""IMDb 标题搜索应直接调用 IMDb 免 Key 建议接口并缓存响应。"""
|
||||
api = ImdbApi()
|
||||
api.clear_cache()
|
||||
api._request = Mock()
|
||||
api._request.get_json.return_value = {
|
||||
"d": [
|
||||
{
|
||||
"id": "tt0111161",
|
||||
"qid": "movie",
|
||||
"l": "The Shawshank Redemption",
|
||||
"y": 1994,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
results = api.search_titles("The Shawshank Redemption", limit=1)
|
||||
cached_results = api.search_titles("The Shawshank Redemption", limit=1)
|
||||
|
||||
assert [item.id for item in results] == ["tt0111161"]
|
||||
assert [item.id for item in cached_results] == ["tt0111161"]
|
||||
api._request.get_json.assert_called_once_with(
|
||||
"https://v2.sg.media-imdb.com/suggestion/x/"
|
||||
"The%20Shawshank%20Redemption.json",
|
||||
params={},
|
||||
)
|
||||
|
||||
api.clear_cache()
|
||||
api.search_titles("The Shawshank Redemption", limit=1)
|
||||
assert api._request.get_json.call_count == 2
|
||||
|
||||
|
||||
def test_imdb_graphql_error_is_not_cached() -> None:
|
||||
"""GraphQL 错误响应不应污染缓存,后续成功详情仍应正常写入缓存。"""
|
||||
api = ImdbApi()
|
||||
api.clear_cache()
|
||||
api._request = Mock()
|
||||
api._request.post_json.side_effect = [
|
||||
{"errors": [{"message": "temporary failure"}]},
|
||||
{
|
||||
"data": {
|
||||
"titles": [
|
||||
{
|
||||
"id": "tt0111161",
|
||||
"titleText": {"text": "The Shawshank Redemption"},
|
||||
"titleType": {"id": "movie"},
|
||||
"releaseYear": {"year": 1994},
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
assert api.get_title("tt0111161") is None
|
||||
detail = api.get_title("tt0111161")
|
||||
cached_detail = api.get_title("tt0111161")
|
||||
|
||||
assert detail and detail.primary_title == "The Shawshank Redemption"
|
||||
assert cached_detail and cached_detail.id == "tt0111161"
|
||||
assert api._request.post_json.call_count == 2
|
||||
|
||||
|
||||
def test_async_imdb_api_merges_paginated_episodes() -> None:
|
||||
"""IMDb 异步剧集查询应使用 GraphQL 游标合并并缓存所有分页。"""
|
||||
api = ImdbApi()
|
||||
asyncio.run(api.async_clear_cache())
|
||||
api._async_request = Mock()
|
||||
api._async_request.post_json = AsyncMock(
|
||||
side_effect=[
|
||||
{
|
||||
"data": {
|
||||
"titles": [
|
||||
{
|
||||
"episodes": {
|
||||
"episodes": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"id": "tt-first",
|
||||
"series": {
|
||||
"episodeNumber": {
|
||||
"seasonNumber": 1,
|
||||
"episodeNumber": 1,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"pageInfo": {
|
||||
"hasNextPage": True,
|
||||
"endCursor": "next-page",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"titles": [
|
||||
{
|
||||
"episodes": {
|
||||
"episodes": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"id": "tt-second",
|
||||
"series": {
|
||||
"episodeNumber": {
|
||||
"seasonNumber": 1,
|
||||
"episodeNumber": 2,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"pageInfo": {
|
||||
"hasNextPage": False,
|
||||
"endCursor": None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"titles": [
|
||||
{
|
||||
"episodes": {
|
||||
"episodes": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"id": "tt-first",
|
||||
"series": {
|
||||
"episodeNumber": {
|
||||
"seasonNumber": 1,
|
||||
"episodeNumber": 1,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"pageInfo": {
|
||||
"hasNextPage": True,
|
||||
"endCursor": "next-page",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"titles": [
|
||||
{
|
||||
"episodes": {
|
||||
"episodes": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"id": "tt-second",
|
||||
"series": {
|
||||
"episodeNumber": {
|
||||
"seasonNumber": 1,
|
||||
"episodeNumber": 2,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"pageInfo": {
|
||||
"hasNextPage": False,
|
||||
"endCursor": None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
results = asyncio.run(api.async_list_episodes("tt-series"))
|
||||
cached_results = asyncio.run(api.async_list_episodes("tt-series"))
|
||||
|
||||
assert [item.id for item in results] == ["tt-first", "tt-second"]
|
||||
assert [item.id for item in cached_results] == ["tt-first", "tt-second"]
|
||||
assert api._async_request.post_json.await_count == 2
|
||||
assert api._async_request.post_json.await_args_list[0].kwargs["json"][
|
||||
"variables"
|
||||
] == {"after": None, "first": 100, "titles": ("tt-series",)}
|
||||
assert api._async_request.post_json.await_args_list[1].kwargs["json"][
|
||||
"variables"
|
||||
] == {"after": "next-page", "first": 100, "titles": ("tt-series",)}
|
||||
|
||||
asyncio.run(api.async_clear_cache())
|
||||
refreshed_results = asyncio.run(api.async_list_episodes("tt-series"))
|
||||
assert [item.id for item in refreshed_results] == ["tt-first", "tt-second"]
|
||||
assert api._async_request.post_json.await_count == 4
|
||||
|
||||
|
||||
def test_imdb_proxy_snapshot_reloads_client_and_closes_old_session(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""代理变更应关闭旧 IMDb 客户端并按新代理快照重建。"""
|
||||
module = ImdbModule()
|
||||
old_client = Mock()
|
||||
new_client = Mock()
|
||||
monkeypatch.setattr(settings, "PROXY_HOST", "http://old-proxy:7890")
|
||||
|
||||
with patch("app.modules.imdb.ImdbApi", side_effect=[old_client, new_client]) as api_type:
|
||||
module.init_module()
|
||||
old_proxy = settings.PROXY
|
||||
monkeypatch.setattr(settings, "PROXY_HOST", "http://new-proxy:7890")
|
||||
new_proxy = settings.PROXY
|
||||
module.on_config_changed()
|
||||
|
||||
assert api_type.call_args_list[0].kwargs["proxies"] == old_proxy
|
||||
assert api_type.call_args_list[1].kwargs["proxies"] == new_proxy
|
||||
old_client.close.assert_called_once_with()
|
||||
assert module.imdb_api is new_client
|
||||
|
||||
|
||||
def test_imdb_module_clear_cache_delegates_to_api(
|
||||
imdb_module: ImdbModule,
|
||||
) -> None:
|
||||
"""系统统一清缓存动作应清理 IMDb HTTP 缓存区。"""
|
||||
imdb_module.clear_cache()
|
||||
|
||||
imdb_module.imdb_api.clear_cache.assert_called_once_with()
|
||||
|
||||
|
||||
def test_imdb_search_respects_source_and_returns_generic_identity(
|
||||
imdb_module: ImdbModule,
|
||||
) -> None:
|
||||
"""IMDb 搜索只响应选中的来源,并返回来源与原生 ID 对。"""
|
||||
meta = MetaBase("Breaking Bad")
|
||||
meta.name = "Breaking Bad"
|
||||
meta.type = MediaType.TV
|
||||
|
||||
assert imdb_module.search_medias(meta, media_source=MediaSource.Douban) is None
|
||||
|
||||
results = imdb_module.search_medias(meta, media_source=MediaSource.IMDb)
|
||||
|
||||
assert results and len(results) == 1
|
||||
assert results[0].media_source == MediaSource.IMDb
|
||||
assert results[0].media_id == "tt0903747"
|
||||
assert results[0].imdb_id == "tt0903747"
|
||||
assert results[0].type == MediaType.TV
|
||||
|
||||
|
||||
def test_imdb_explicit_identity_recognition_enriches_media_info(
|
||||
imdb_module: ImdbModule,
|
||||
) -> None:
|
||||
"""显式 IMDb ID 识别应补齐别名、演职员、剧集和图片。"""
|
||||
result = imdb_module.recognize_media(
|
||||
media_source=MediaSource.IMDb,
|
||||
media_id="TT0903747",
|
||||
)
|
||||
|
||||
assert isinstance(result, MediaInfo)
|
||||
assert result.media_source == MediaSource.IMDb
|
||||
assert result.media_id == "tt0903747"
|
||||
assert result.title == "Breaking Bad"
|
||||
assert result.names == ["Breaking Bad", "绝命毒师"]
|
||||
assert result.backdrop_path == "https://image.example/backdrop.jpg"
|
||||
assert result.directors[0]["name"] == "Vince Gilligan"
|
||||
assert result.actors[0]["character"] == "Walter White"
|
||||
assert result.seasons == {1: [1]}
|
||||
assert result.season_years == {1: "2008"}
|
||||
assert result.number_of_episodes == 1
|
||||
assert result.runtime == 47
|
||||
assert result.category == "欧美剧"
|
||||
|
||||
|
||||
def test_imdb_name_recognition_requires_imdb_selection(
|
||||
imdb_module: ImdbModule, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""名称识别仅在请求级或全局来源选中 IMDb 时执行。"""
|
||||
meta = MetaBase("绝命毒师")
|
||||
meta.name = "绝命毒师"
|
||||
meta.cn_name = "绝命毒师"
|
||||
meta.type = MediaType.TV
|
||||
monkeypatch.setattr(settings, "RECOGNIZE_SOURCE", MediaSource.TMDB.value)
|
||||
|
||||
assert imdb_module.recognize_media(meta=meta) is None
|
||||
|
||||
result = imdb_module.recognize_media(
|
||||
meta=meta,
|
||||
media_source=MediaSource.IMDb,
|
||||
)
|
||||
|
||||
assert result and result.media_id == "tt0903747"
|
||||
imdb_module.imdb_api.search_titles.assert_called_with("绝命毒师")
|
||||
|
||||
|
||||
def test_imdb_recognition_rejects_invalid_or_music_identity(
|
||||
imdb_module: ImdbModule,
|
||||
) -> None:
|
||||
"""IMDb Module 不应接管非法 ID 或音乐识别请求。"""
|
||||
assert (
|
||||
imdb_module.recognize_media(
|
||||
media_source=MediaSource.IMDb,
|
||||
media_id="0903747",
|
||||
)
|
||||
is None
|
||||
)
|
||||
assert (
|
||||
imdb_module.recognize_media(
|
||||
mtype=MediaType.MUSIC,
|
||||
media_source=MediaSource.IMDb,
|
||||
media_id="tt0903747",
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_async_imdb_recognition_uses_async_detail_pipeline(
|
||||
imdb_module: ImdbModule,
|
||||
) -> None:
|
||||
"""异步显式识别应使用异步详情管线并保持统一身份。"""
|
||||
result = asyncio.run(
|
||||
imdb_module.async_recognize_media(
|
||||
media_source=MediaSource.IMDb,
|
||||
media_id="tt0903747",
|
||||
)
|
||||
)
|
||||
|
||||
assert result and result.media_source == MediaSource.IMDb
|
||||
assert result.media_id == "tt0903747"
|
||||
assert result.backdrop_path == "https://image.example/backdrop.jpg"
|
||||
imdb_module.imdb_api.async_get_title.assert_awaited()
|
||||
imdb_module.imdb_api.async_list_credits.assert_awaited_once_with("tt0903747")
|
||||
|
||||
|
||||
def test_imdb_scraping_only_handles_imdb_source(
|
||||
imdb_module: ImdbModule,
|
||||
) -> None:
|
||||
"""IMDb NFO 和图片刮削只响应显式 IMDb 刮削来源。"""
|
||||
media_info = MediaInfo()
|
||||
media_info.scrape_source = MediaSource.Douban.value
|
||||
assert imdb_module.metadata_nfo(media_info) is None
|
||||
assert imdb_module.metadata_img(media_info) is None
|
||||
|
||||
media_info.scrape_source = MediaSource.IMDb.value
|
||||
imdb_module.scraper.get_metadata_nfo.return_value = "<movie />"
|
||||
imdb_module.scraper.get_metadata_img.return_value = {"poster.jpg": "url"}
|
||||
|
||||
assert imdb_module.metadata_nfo(media_info) == "<movie />"
|
||||
assert imdb_module.metadata_img(media_info) == {"poster.jpg": "url"}
|
||||
@@ -490,7 +490,7 @@ from app.runtime.extensions.host_module_adapter import (
|
||||
|
||||
registry = build_host_module_registry()
|
||||
specs = registry.list_specs()
|
||||
assert len(specs) == 37
|
||||
assert len(specs) == 38
|
||||
|
||||
adapter = HostModuleAdapter()
|
||||
lifecycle_events = []
|
||||
@@ -537,7 +537,7 @@ from app.schemas.types import EventType
|
||||
|
||||
registry = build_host_module_registry()
|
||||
specs = registry.list_specs()
|
||||
assert len(specs) == 37
|
||||
assert len(specs) == 38
|
||||
spec_by_id = {spec.id: spec for spec in specs}
|
||||
|
||||
events = {spec.id: [] for spec in specs}
|
||||
@@ -704,7 +704,7 @@ from app.runtime.extensions.host_module_adapter import (
|
||||
|
||||
registry = build_host_module_registry()
|
||||
specs = registry.list_specs()
|
||||
assert len(specs) == 37
|
||||
assert len(specs) == 38
|
||||
configured_specs = tuple(
|
||||
spec for spec in specs
|
||||
if spec.activation is ActivationPolicy.WHEN_CONFIGURED
|
||||
@@ -800,12 +800,12 @@ from app.application.module import configure_module_runtime
|
||||
configure_module_runtime(lambda: ModuleManager())
|
||||
|
||||
manager = ModuleManager()
|
||||
assert len(manager.list_specs()) == 37
|
||||
assert len(manager.list_specs()) == 38
|
||||
assert manager.get_specs() == manager.list_specs()
|
||||
|
||||
from app.api.endpoints.system import modulelist
|
||||
response = modulelist(None)
|
||||
assert len(response.data["modules"]) == 37
|
||||
assert len(response.data["modules"]) == 38
|
||||
|
||||
heavy_prefixes = (
|
||||
"lark_oapi",
|
||||
@@ -913,7 +913,7 @@ from app.runtime.extensions.module_manager import ModuleManager
|
||||
|
||||
manager = ModuleManager()
|
||||
modules = manager.get_modules()
|
||||
assert len(modules) == len(manager.list_specs()) == 37
|
||||
assert len(modules) == len(manager.list_specs()) == 38
|
||||
for spec in manager.list_specs():
|
||||
implementation = modules[spec.id]
|
||||
assert implementation.get_name() == spec.metadata["name"]
|
||||
|
||||
Reference in New Issue
Block a user