mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-09 09:26:55 +08:00
fix: release plugin media sources from built-in declarations
This commit is contained in:
@@ -1706,12 +1706,7 @@
|
|||||||
"tvdb",
|
"tvdb",
|
||||||
"musicbrainz",
|
"musicbrainz",
|
||||||
"theaudiodb",
|
"theaudiodb",
|
||||||
"doubanmusic",
|
"doubanmusic"
|
||||||
"bilibili",
|
|
||||||
"mangguodiscover",
|
|
||||||
"migu",
|
|
||||||
"tencentvideodiscover",
|
|
||||||
"iqiyidiscover"
|
|
||||||
],
|
],
|
||||||
"pattern": "^[a-z][a-z0-9._-]{0,63}$",
|
"pattern": "^[a-z][a-z0-9._-]{0,63}$",
|
||||||
"title": "MediaSource",
|
"title": "MediaSource",
|
||||||
|
|||||||
@@ -64,11 +64,6 @@ _BUILTIN_MEDIA_SOURCES = (
|
|||||||
media_source=MediaSource.DoubanMusic,
|
media_source=MediaSource.DoubanMusic,
|
||||||
media_types=[MediaType.MUSIC],
|
media_types=[MediaType.MUSIC],
|
||||||
),
|
),
|
||||||
_SchemaMediaSourceInfo(name="哔哩哔哩", media_source=MediaSource.Bilibili),
|
|
||||||
_SchemaMediaSourceInfo(name="芒果TV", media_source=MediaSource.MangoTV),
|
|
||||||
_SchemaMediaSourceInfo(name="咪咕视频", media_source=MediaSource.MiguVideo),
|
|
||||||
_SchemaMediaSourceInfo(name="腾讯视频", media_source=MediaSource.TencentVideo),
|
|
||||||
_SchemaMediaSourceInfo(name="爱奇艺", media_source=MediaSource.Iqiyi),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,15 +18,8 @@ FIXTURE_CLASSIFICATION_SOURCES: Final[tuple[str, ...]] = (
|
|||||||
)
|
)
|
||||||
"""首版分类体系具备真实标准投影 fixture 的内置来源顺序。"""
|
"""首版分类体系具备真实标准投影 fixture 的内置来源顺序。"""
|
||||||
|
|
||||||
BUILTIN_CLASSIFICATION_SOURCES: Final[tuple[str, ...]] = (
|
BUILTIN_CLASSIFICATION_SOURCES: Final[tuple[str, ...]] = FIXTURE_CLASSIFICATION_SOURCES
|
||||||
*FIXTURE_CLASSIFICATION_SOURCES,
|
"""仅保留宿主模块实现的来源;插件来源由启用插件声明,不预占标识。"""
|
||||||
MediaSource.Bilibili.value,
|
|
||||||
MediaSource.MangoTV.value,
|
|
||||||
MediaSource.MiguVideo.value,
|
|
||||||
MediaSource.TencentVideo.value,
|
|
||||||
MediaSource.Iqiyi.value,
|
|
||||||
)
|
|
||||||
"""媒体来源 API 当前暴露的全部内置来源顺序。"""
|
|
||||||
|
|
||||||
STANDARD_CLASSIFICATION_FIELD_IDS: Final[tuple[str, ...]] = (
|
STANDARD_CLASSIFICATION_FIELD_IDS: Final[tuple[str, ...]] = (
|
||||||
"identity.media_source",
|
"identity.media_source",
|
||||||
@@ -155,11 +148,6 @@ _SOURCE_FIELD_SUPPORT: Final[dict[str, dict[str, ClassificationSourceSupport]]]
|
|||||||
"music.genres": _PARTIAL,
|
"music.genres": _PARTIAL,
|
||||||
"music.tags": _PARTIAL,
|
"music.tags": _PARTIAL,
|
||||||
},
|
},
|
||||||
MediaSource.Bilibili.value: {},
|
|
||||||
MediaSource.MangoTV.value: {},
|
|
||||||
MediaSource.MiguVideo.value: {},
|
|
||||||
MediaSource.TencentVideo.value: {},
|
|
||||||
MediaSource.Iqiyi.value: {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-14
@@ -900,22 +900,9 @@ class DiscoverMediaSource(BaseModel):
|
|||||||
if media_source and not mediaid_prefix:
|
if media_source and not mediaid_prefix:
|
||||||
normalized["mediaid_prefix"] = str(media_source)
|
normalized["mediaid_prefix"] = str(media_source)
|
||||||
elif mediaid_prefix and not media_source:
|
elif mediaid_prefix and not media_source:
|
||||||
normalized["media_source"] = cls._media_source_from_prefix(
|
normalized["media_source"] = MediaSource(str(mediaid_prefix))
|
||||||
str(mediaid_prefix)
|
|
||||||
)
|
|
||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _media_source_from_prefix(mediaid_prefix: str) -> MediaSource:
|
|
||||||
"""将旧插件前缀映射为内置或插件扩展媒体来源。"""
|
|
||||||
aliases = {
|
|
||||||
"mangguo": MediaSource.MangoTV,
|
|
||||||
"tencentvideo": MediaSource.TencentVideo,
|
|
||||||
}
|
|
||||||
if mediaid_prefix in aliases:
|
|
||||||
return aliases[mediaid_prefix]
|
|
||||||
return MediaSource(mediaid_prefix)
|
|
||||||
|
|
||||||
|
|
||||||
class MediaSourceInfo(BaseModel):
|
class MediaSourceInfo(BaseModel):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -28,15 +28,6 @@ MEDIA_SOURCE_ALIASES = {
|
|||||||
"audio_db": MediaSource.TheAudioDB,
|
"audio_db": MediaSource.TheAudioDB,
|
||||||
"doubanmusic": MediaSource.DoubanMusic,
|
"doubanmusic": MediaSource.DoubanMusic,
|
||||||
"douban_music": MediaSource.DoubanMusic,
|
"douban_music": MediaSource.DoubanMusic,
|
||||||
"bilibili": MediaSource.Bilibili,
|
|
||||||
"mangguodiscover": MediaSource.MangoTV,
|
|
||||||
"mango_tv": MediaSource.MangoTV,
|
|
||||||
"migu": MediaSource.MiguVideo,
|
|
||||||
"migu_video": MediaSource.MiguVideo,
|
|
||||||
"tencentvideodiscover": MediaSource.TencentVideo,
|
|
||||||
"tencent_video": MediaSource.TencentVideo,
|
|
||||||
"iqiyi": MediaSource.Iqiyi,
|
|
||||||
"iqiyidiscover": MediaSource.Iqiyi,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MEDIA_SOURCE_PREFIXES = {
|
MEDIA_SOURCE_PREFIXES = {
|
||||||
@@ -49,11 +40,6 @@ MEDIA_SOURCE_PREFIXES = {
|
|||||||
MediaSource.MusicBrainz: "musicbrainz",
|
MediaSource.MusicBrainz: "musicbrainz",
|
||||||
MediaSource.TheAudioDB: "theaudiodb",
|
MediaSource.TheAudioDB: "theaudiodb",
|
||||||
MediaSource.DoubanMusic: "doubanmusic",
|
MediaSource.DoubanMusic: "doubanmusic",
|
||||||
MediaSource.Bilibili: "bilibili",
|
|
||||||
MediaSource.MangoTV: "mangguodiscover",
|
|
||||||
MediaSource.MiguVideo: "migu",
|
|
||||||
MediaSource.TencentVideo: "tencentvideodiscover",
|
|
||||||
MediaSource.Iqiyi: "iqiyidiscover",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,10 +85,6 @@ _MEDIA_SOURCE_VALUE_ALIASES = {
|
|||||||
"tmdb": "themoviedb",
|
"tmdb": "themoviedb",
|
||||||
"audio_db": "theaudiodb",
|
"audio_db": "theaudiodb",
|
||||||
"douban_music": "doubanmusic",
|
"douban_music": "doubanmusic",
|
||||||
"mango_tv": "mangguodiscover",
|
|
||||||
"migu_video": "migu",
|
|
||||||
"tencent_video": "tencentvideodiscover",
|
|
||||||
"iqiyi": "iqiyidiscover",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -104,11 +100,6 @@ class MediaSource(str, Enum):
|
|||||||
MusicBrainz = "musicbrainz"
|
MusicBrainz = "musicbrainz"
|
||||||
TheAudioDB = "theaudiodb"
|
TheAudioDB = "theaudiodb"
|
||||||
DoubanMusic = "doubanmusic"
|
DoubanMusic = "doubanmusic"
|
||||||
Bilibili = "bilibili"
|
|
||||||
MangoTV = "mangguodiscover"
|
|
||||||
MiguVideo = "migu"
|
|
||||||
TencentVideo = "tencentvideodiscover"
|
|
||||||
Iqiyi = "iqiyidiscover"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""返回可直接用于 API 和数据库的规范值。"""
|
"""返回可直接用于 API 和数据库的规范值。"""
|
||||||
|
|||||||
+3
-1
@@ -265,7 +265,9 @@ FastAPI 的 HTTP 异常和参数校验异常统一使用 `message`,不再返
|
|||||||
|
|
||||||
#### 媒体识别 / 整理
|
#### 媒体识别 / 整理
|
||||||
|
|
||||||
媒体识别、搜索和手动整理统一使用 `media_source` + `media_id` 表示媒体主身份。内置来源通过 `MediaSource` 提供 `themoviedb`、`douban`、`bangumi`、`anilist`、`imdb`、`tvdb`、`musicbrainz`、`theaudiodb`、`doubanmusic`、`bilibili`、`mangguodiscover`、`migu` 和 `tencentvideodiscover` 等常量;该列表不是插件来源白名单,插件可以注册符合 OpenAPI 格式约束的稳定扩展标识。`media_id` 是该来源的原生 ID,不添加 `tmdb:` 等前缀。需要精确身份时两个字段必须同时提供,不能只传其中一个。
|
媒体识别、搜索和手动整理统一使用 `media_source` + `media_id` 表示媒体主身份。内置来源通过 `MediaSource` 提供 `themoviedb`、`douban`、`bangumi`、`anilist`、`imdb`、`tvdb`、`musicbrainz`、`theaudiodb`、`doubanmusic` 九个具有宿主模块实现的常量;该列表不是插件来源白名单,插件可以注册符合 OpenAPI 格式约束的稳定扩展标识。`media_id` 是该来源的原生 ID,不添加 `tmdb:` 等前缀。需要精确身份时两个字段必须同时提供,不能只传其中一个。
|
||||||
|
|
||||||
|
媒体来源列表 `/api/v1/media/source` 仅预置上述九个来源,其余来源由启用插件注册后提供。哔哩哔哩、芒果 TV、咪咕视频、腾讯视频、爱奇艺不再占用内置来源标识,宿主也不再转换这些插件来源的旧别名;调用方应使用插件声明的准确来源 ID。
|
||||||
|
|
||||||
影视自动识别在未指定来源时只使用 TMDB,未命中时不会继续查询其它影视源。音乐路径识别严格按 AcoustID 音频指纹、文件标签、文件名三级依次执行;指纹或标签直接提供 MusicBrainz Recording ID 时,会直接查询 MusicBrainz 详情,标签和文件名标题识别也只使用 MusicBrainz。其它元数据源仅在手动操作通过请求级 `media_source`,或通过完整的 `media_source` + `media_id` 精确指定时使用,不修改系统默认值,也不会跨来源兜底。`MediaInfo` 响应仍可能包含 `tmdb_id`、`douban_id`、`bangumi_id`、`anilist_id` 等跨源映射辅助字段,但这些字段不是通用请求入口。明确归属 `/tmdb`、`/douban`、`/bangumi`、`/anilist` 的接口,以及固定使用 TMDB 的剧集组和排期接口,仍可按其单数据源契约接收原生 ID。
|
影视自动识别在未指定来源时只使用 TMDB,未命中时不会继续查询其它影视源。音乐路径识别严格按 AcoustID 音频指纹、文件标签、文件名三级依次执行;指纹或标签直接提供 MusicBrainz Recording ID 时,会直接查询 MusicBrainz 详情,标签和文件名标题识别也只使用 MusicBrainz。其它元数据源仅在手动操作通过请求级 `media_source`,或通过完整的 `media_source` + `media_id` 精确指定时使用,不修改系统默认值,也不会跨来源兜底。`MediaInfo` 响应仍可能包含 `tmdb_id`、`douban_id`、`bangumi_id`、`anilist_id` 等跨源映射辅助字段,但这些字段不是通用请求入口。明确归属 `/tmdb`、`/douban`、`/bangumi`、`/anilist` 的接口,以及固定使用 TMDB 的剧集组和排期接口,仍可按其单数据源契约接收原生 ID。
|
||||||
|
|
||||||
|
|||||||
@@ -552,6 +552,7 @@ Purpose: List seasons for one exact media identity or a title-and-year fallback.
|
|||||||
### `media.sources`
|
### `media.sources`
|
||||||
`GET /api/v1/media/source`; policy effect: `safe_read`.
|
`GET /api/v1/media/source`; policy effect: `safe_read`.
|
||||||
Purpose: List metadata sources currently registered for MoviePilot media operations.
|
Purpose: List metadata sources currently registered for MoviePilot media operations.
|
||||||
|
Only sources implemented by host modules are built in. Other sources appear after an enabled plugin registers them; use the exact returned identifier without converting plugin source aliases.
|
||||||
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
|
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
|
||||||
- `path_params`: none
|
- `path_params`: none
|
||||||
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
|
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
|
||||||
|
|||||||
@@ -158,8 +158,8 @@ def test_collection_json_schemas_define_items_or_tuple_members():
|
|||||||
assert crew_schema["items"]["$ref"].endswith("/TmdbEpisodeCrew")
|
assert crew_schema["items"]["$ref"].endswith("/TmdbEpisodeCrew")
|
||||||
|
|
||||||
|
|
||||||
def test_discover_media_source_keeps_legacy_prefix_compatible():
|
def test_discover_media_source_preserves_plugin_identifiers():
|
||||||
"""发现源应兼容旧插件前缀,并同时输出规范媒体来源。"""
|
"""发现源按插件标识补齐身份字段,不转换插件特定前缀。"""
|
||||||
legacy = DiscoverMediaSource(
|
legacy = DiscoverMediaSource(
|
||||||
name="哔哩哔哩",
|
name="哔哩哔哩",
|
||||||
mediaid_prefix="bilibili",
|
mediaid_prefix="bilibili",
|
||||||
@@ -167,7 +167,7 @@ def test_discover_media_source_keeps_legacy_prefix_compatible():
|
|||||||
)
|
)
|
||||||
current = DiscoverMediaSource(
|
current = DiscoverMediaSource(
|
||||||
name="腾讯视频",
|
name="腾讯视频",
|
||||||
media_source=MediaSource.TencentVideo,
|
media_source=MediaSource("tencentvideodiscover"),
|
||||||
api_path="plugin/TencentVideoDiscover/discover",
|
api_path="plugin/TencentVideoDiscover/discover",
|
||||||
)
|
)
|
||||||
historical_alias = DiscoverMediaSource(
|
historical_alias = DiscoverMediaSource(
|
||||||
@@ -181,9 +181,9 @@ def test_discover_media_source_keeps_legacy_prefix_compatible():
|
|||||||
api_path="plugin/AcmeVideo/discover",
|
api_path="plugin/AcmeVideo/discover",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert legacy.media_source is MediaSource.Bilibili
|
assert legacy.media_source == MediaSource("bilibili")
|
||||||
assert legacy.model_dump(mode="json")["mediaid_prefix"] == "bilibili"
|
assert legacy.model_dump(mode="json")["mediaid_prefix"] == "bilibili"
|
||||||
assert current.mediaid_prefix == MediaSource.TencentVideo.value
|
assert current.mediaid_prefix == "tencentvideodiscover"
|
||||||
assert historical_alias.media_source is MediaSource.MangoTV
|
assert historical_alias.media_source == MediaSource("mangguo")
|
||||||
assert plugin_source.media_source == MediaSource("acme.video")
|
assert plugin_source.media_source == MediaSource("acme.video")
|
||||||
assert plugin_source.mediaid_prefix == "acme.video"
|
assert plugin_source.mediaid_prefix == "acme.video"
|
||||||
|
|||||||
@@ -98,20 +98,16 @@ def test_catalog_separates_retired_fields_from_new_rule_options() -> None:
|
|||||||
assert retired_fields[0].replacement_field == "media.countries"
|
assert retired_fields[0].replacement_field == "media.countries"
|
||||||
|
|
||||||
|
|
||||||
def test_discover_only_builtin_sources_are_explicitly_unavailable() -> None:
|
def test_plugin_sources_have_no_builtin_capability_declarations() -> None:
|
||||||
"""仅提供发现入口的内置来源不能被误认为可形成分类事实。"""
|
"""无宿主模块的来源不得预占插件标识或预设不可用的字段能力。"""
|
||||||
discover_sources = {
|
discover_sources = {
|
||||||
MediaSource.Bilibili.value,
|
"bilibili", "mangguodiscover", "migu", "tencentvideodiscover", "iqiyidiscover",
|
||||||
MediaSource.MangoTV.value,
|
|
||||||
MediaSource.MiguVideo.value,
|
|
||||||
MediaSource.TencentVideo.value,
|
|
||||||
MediaSource.Iqiyi.value,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for media_source in discover_sources:
|
assert discover_sources.isdisjoint(BUILTIN_CLASSIFICATION_SOURCES)
|
||||||
assert set(builtin_source_field_support(media_source).values()) == {
|
assert discover_sources.isdisjoint(source.value for source in MediaSource)
|
||||||
"unavailable"
|
for field in get_standard_classification_fields():
|
||||||
}
|
assert discover_sources.isdisjoint(field.source_support)
|
||||||
|
|
||||||
|
|
||||||
def test_catalog_dictionary_values_match_normalized_facts() -> None:
|
def test_catalog_dictionary_values_match_normalized_facts() -> None:
|
||||||
|
|||||||
@@ -306,6 +306,61 @@ def test_registry_rejects_builtin_duplicate_and_cross_plugin_sources() -> None:
|
|||||||
assert registry.sources(OTHER_PLUGIN_ID) == []
|
assert registry.sources(OTHER_PLUGIN_ID) == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"media_source",
|
||||||
|
["bilibili", "mangguodiscover", "migu", "tencentvideodiscover", "iqiyidiscover"],
|
||||||
|
)
|
||||||
|
def test_plugin_owned_sources_follow_registration_lifecycle(
|
||||||
|
media_source: str,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""无内置模块的来源由插件注册,API 和分类字段随插件启停增删。"""
|
||||||
|
from app.api.endpoints.media import _registered_media_sources
|
||||||
|
from app.application.classification.catalog import build_classification_field_catalog
|
||||||
|
from app.domain.classification.sources import BUILTIN_CLASSIFICATION_SOURCES
|
||||||
|
|
||||||
|
registry = _registry()
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"app.application.plugin.runtime.get_plugin_manager",
|
||||||
|
lambda: SimpleNamespace(get_media_sources=registry.sources),
|
||||||
|
)
|
||||||
|
source_id = media_source
|
||||||
|
field_id = f"extensions.{source_id}.region_group"
|
||||||
|
baseline_sources = _registered_media_sources()
|
||||||
|
assert {source.media_source.value for source in baseline_sources} == set(
|
||||||
|
BUILTIN_CLASSIFICATION_SOURCES
|
||||||
|
)
|
||||||
|
assert media_source not in {source.media_source for source in baseline_sources}
|
||||||
|
assert all(
|
||||||
|
source_id not in field.source_support
|
||||||
|
for field in build_classification_field_catalog()
|
||||||
|
)
|
||||||
|
|
||||||
|
registry.replace(PLUGIN_ID, [_source(
|
||||||
|
source_id=source_id,
|
||||||
|
fields=[_field("region_group", source_id=source_id)],
|
||||||
|
)])
|
||||||
|
sources = _registered_media_sources()
|
||||||
|
assert [source for source in sources if source.media_source == media_source] == [
|
||||||
|
MediaSourceInfo.model_validate(registry.sources()[0])
|
||||||
|
]
|
||||||
|
fields = {field.id: field for field in build_classification_field_catalog(registry.fields())}
|
||||||
|
assert fields[field_id].source_support == {source_id: "extension"}
|
||||||
|
media = MediaInfo(
|
||||||
|
media_source=media_source,
|
||||||
|
media_id="native-1",
|
||||||
|
type=MediaType.MOVIE,
|
||||||
|
classification_facts={field_id: "亚洲"},
|
||||||
|
)
|
||||||
|
assert registry.facts(media) == {source_id: {"region_group": "亚洲"}}
|
||||||
|
|
||||||
|
registry.remove(PLUGIN_ID)
|
||||||
|
assert _registered_media_sources() == baseline_sources
|
||||||
|
assert field_id not in {
|
||||||
|
field.id for field in build_classification_field_catalog(registry.fields())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("field", "error_text"),
|
("field", "error_text"),
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -26,13 +26,14 @@ def test_generic_source_id_resolves_as_fixed_enum() -> None:
|
|||||||
def test_iqiyi_source_routes_through_unified_identity() -> None:
|
def test_iqiyi_source_routes_through_unified_identity() -> None:
|
||||||
"""爱奇艺探索来源应支持选择解析、身份解析和媒体键构造。"""
|
"""爱奇艺探索来源应支持选择解析、身份解析和媒体键构造。"""
|
||||||
assert parse_media_source_selection("iqiyi,iqiyidiscover") == (
|
assert parse_media_source_selection("iqiyi,iqiyidiscover") == (
|
||||||
MediaSource.Iqiyi,
|
MediaSource("iqiyi"), MediaSource("iqiyidiscover"),
|
||||||
)
|
)
|
||||||
assert resolve_media_identity(
|
assert resolve_media_identity(
|
||||||
media_source="iqiyi",
|
media_source="iqiyidiscover",
|
||||||
media_id="album-1",
|
media_id="album-1",
|
||||||
) == (MediaSource.Iqiyi, "album-1")
|
) == (MediaSource("iqiyidiscover"), "album-1")
|
||||||
assert build_media_key("iqiyi", "album-1") == "iqiyidiscover:album-1"
|
assert build_media_key("iqiyidiscover", "album-1") == "iqiyidiscover:album-1"
|
||||||
|
assert build_media_key("iqiyi", "album-1") == "iqiyi:album-1"
|
||||||
|
|
||||||
|
|
||||||
def test_plugin_source_is_preserved_as_dynamic_enum() -> None:
|
def test_plugin_source_is_preserved_as_dynamic_enum() -> None:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import pytest
|
|||||||
from app.api.endpoints import media as media_endpoint
|
from app.api.endpoints import media as media_endpoint
|
||||||
from app.api.endpoints import search as search_endpoint
|
from app.api.endpoints import search as search_endpoint
|
||||||
from app.chain.subscribe import create as subscribe_create
|
from app.chain.subscribe import create as subscribe_create
|
||||||
from app.chain.subscribe import SubscribeChain
|
from app.chain.subscribe.facade import SubscribeChain
|
||||||
from app.domain.context import MediaInfo
|
from app.domain.context import MediaInfo
|
||||||
from app.schemas.types import MediaSource, MediaType
|
from app.schemas.types import MediaSource, MediaType
|
||||||
from app.schemas.media import normalize_media_source
|
from app.schemas.media import normalize_media_source
|
||||||
@@ -21,11 +21,16 @@ def test_media_source_normalization_accepts_plugin_source() -> None:
|
|||||||
assert normalize_media_source("plugin source:invalid") is None
|
assert normalize_media_source("plugin source:invalid") is None
|
||||||
|
|
||||||
|
|
||||||
def test_iqiyi_media_source_aliases_are_normalized() -> None:
|
@pytest.mark.parametrize("source_id", [
|
||||||
"""爱奇艺探索来源的历史前缀和规范前缀应归一到同一媒体来源。"""
|
"bilibili", "mangguodiscover", "migu", "tencentvideodiscover", "iqiyidiscover",
|
||||||
assert normalize_media_source("iqiyi") is MediaSource.Iqiyi
|
"mango_tv", "migu_video", "tencent_video", "iqiyi",
|
||||||
assert normalize_media_source("iqiyidiscover") is MediaSource.Iqiyi
|
])
|
||||||
assert MediaSource("iqiyi") is MediaSource.Iqiyi
|
def test_plugin_media_sources_preserve_exact_identifiers(source_id: str) -> None:
|
||||||
|
"""插件来源不再属于内置枚举,也不替插件转换来源别名。"""
|
||||||
|
source = normalize_media_source(source_id)
|
||||||
|
assert source is MediaSource(source_id)
|
||||||
|
assert source.value == source_id
|
||||||
|
assert source not in list(MediaSource)
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_anilist_search_params_preserves_identity() -> None:
|
def test_resolve_anilist_search_params_preserves_identity() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user