Files
MoviePilot/tests/test_mediaserver_tv_stale_itemid.py

230 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import unittest
from types import SimpleNamespace
from unittest.mock import Mock, patch
from plexapi.exceptions import NotFound
from app.modules.emby.emby import Emby
from app.modules.jellyfin.jellyfin import Jellyfin
from app.modules.plex.plex import Plex
from app.modules.trimemedia.trimemedia import TrimeMedia
from app.modules.ugreen.ugreen import Ugreen
from app.modules.zspace.zspace import ZSpace
class _FakeResponse:
"""提供媒体服务器接口响应的最小json封装。"""
def __init__(self, payload: dict):
self._payload = payload
def json(self):
"""返回测试预置的响应体。"""
return self._payload
class MediaServerTvStaleItemIdTest(unittest.TestCase):
"""验证电视剧缓存ID失效后各媒体服务器适配器会回退到标题搜索。"""
@staticmethod
def _build_plex():
"""构造绕过真实连接的Plex实例便于单测直接注入plexapi mock。"""
plex = Plex.__new__(Plex)
plex._host = "http://192.168.8.254:32400/"
plex._playhost = None
plex._token = "plex-token"
plex._plex = Mock()
return plex
def test_plex_tv_episodes_fallback_when_cached_item_id_not_found(self):
"""Plex缓存ID失效时应按标题重新搜索并返回新条目的季集。"""
plex = self._build_plex()
plex._plex.fetchItem.side_effect = NotFound("not found")
show = Mock()
show.key = "/library/metadata/200"
show.guids = [{"id": "tmdb://12345"}]
show.episodes.return_value = [
Mock(seasonNumber=1, index=1),
Mock(seasonNumber=1, index=2),
Mock(seasonNumber=2, index=1),
]
plex._plex.library.search.return_value = [show]
item_id, episodes = plex.get_tv_episodes(
item_id="107797",
title="测试剧集",
original_title="Test Show",
year="2026",
tmdb_id=12345,
season=1,
)
self.assertEqual(item_id, "/library/metadata/200")
self.assertEqual(episodes, {1: [1, 2]})
plex._plex.fetchItem.assert_called_once_with(107797)
plex._plex.library.search.assert_called_once_with(title="测试剧集", libtype="show", year="2026")
def test_plex_tv_episodes_returns_empty_when_stale_item_id_search_misses(self):
"""Plex缓存ID失效且标题搜索不到时应返回未入库结果而不是继续抛出404。"""
plex = self._build_plex()
plex._plex.fetchItem.side_effect = NotFound("not found")
plex._plex.library.search.side_effect = [[], []]
item_id, episodes = plex.get_tv_episodes(
item_id="107797",
title="测试剧集",
original_title="Test Show",
year="2026",
)
self.assertIsNone(item_id)
self.assertEqual(episodes, {})
self.assertEqual(plex._plex.library.search.call_count, 2)
def test_plex_tv_episodes_uses_valid_item_id_without_search(self):
"""Plex缓存ID仍有效时应保持直接查询路径避免额外模糊搜索。"""
plex = self._build_plex()
show = Mock()
show.key = "/library/metadata/107797"
show.guids = [{"id": "tmdb://12345"}]
show.episodes.return_value = [Mock(seasonNumber=1, index=1)]
plex._plex.fetchItem.return_value = show
item_id, episodes = plex.get_tv_episodes(
item_id="107797",
title="测试剧集",
tmdb_id=12345,
)
self.assertEqual(item_id, "/library/metadata/107797")
self.assertEqual(episodes, {1: [1]})
plex._plex.fetchItem.assert_called_once_with(107797)
plex._plex.library.search.assert_not_called()
def test_emby_tv_episodes_fallback_when_cached_item_id_missing(self):
"""Emby缓存ID失效时应重新搜索剧集ID后再查询集信息。"""
client = Emby.__new__(Emby)
client._host = "http://emby.local/"
client._apikey = "api-key"
client.user = "user-id"
client.get_iteminfo = Mock(side_effect=[None, SimpleNamespace(tmdbid=12345)])
client._Emby__get_emby_series_id_by_name = Mock(return_value="new-series-id")
with patch("app.modules.emby.emby.RequestUtils") as request_utils_cls:
request_utils_cls.return_value.get_res.return_value = _FakeResponse({
"Items": [{"ParentIndexNumber": 1, "IndexNumber": 1}]
})
item_id, episodes = client.get_tv_episodes(
item_id="old-series-id",
title="测试剧集",
year="2026",
tmdb_id=12345,
)
self.assertEqual(item_id, "new-series-id")
self.assertEqual(episodes, {1: [1]})
client._Emby__get_emby_series_id_by_name.assert_called_once_with("测试剧集", "2026")
def test_jellyfin_tv_episodes_fallback_when_cached_item_id_missing(self):
"""Jellyfin缓存ID失效时应重新搜索剧集ID后再查询集信息。"""
client = Jellyfin.__new__(Jellyfin)
client._host = "http://jellyfin.local/"
client._apikey = "api-key"
client.user = "user-id"
client.get_iteminfo = Mock(side_effect=[None, SimpleNamespace(tmdbid=12345)])
client._Jellyfin__get_jellyfin_series_id_by_name = Mock(return_value="new-series-id")
with patch("app.modules.jellyfin.jellyfin.RequestUtils") as request_utils_cls:
request_utils_cls.return_value.get_res.return_value = _FakeResponse({
"Items": [{"ParentIndexNumber": 1, "IndexNumber": 1}]
})
item_id, episodes = client.get_tv_episodes(
item_id="old-series-id",
title="测试剧集",
year="2026",
tmdb_id=12345,
)
self.assertEqual(item_id, "new-series-id")
self.assertEqual(episodes, {1: [1]})
client._Jellyfin__get_jellyfin_series_id_by_name.assert_called_once_with("测试剧集", "2026")
def test_zspace_tv_episodes_fallback_when_cached_item_id_missing(self):
"""极影视缓存ID失效时应重新搜索剧集ID后再查询集信息。"""
client = ZSpace.__new__(ZSpace)
client._host = "http://zspace.local/"
client._apikey = "api-key"
client.user = "user-id"
client.get_iteminfo = Mock(side_effect=[None, SimpleNamespace(tmdbid=12345)])
client._ZSpace__get_series_id_by_name = Mock(return_value="new-series-id")
with patch("app.modules.zspace.zspace.RequestUtils") as request_utils_cls:
request_utils_cls.return_value.get_res.return_value = _FakeResponse({
"Items": [{"ParentIndexNumber": 1, "IndexNumber": 1}]
})
item_id, episodes = client.get_tv_episodes(
item_id="old-series-id",
title="测试剧集",
year="2026",
tmdb_id=12345,
)
self.assertEqual(item_id, "new-series-id")
self.assertEqual(episodes, {1: [1]})
client._ZSpace__get_series_id_by_name.assert_called_once_with("测试剧集", "2026")
def test_ugreen_tv_episodes_fallback_when_cached_item_id_missing(self):
"""绿联缓存ID失效时应重新搜索剧集ID后再查询集信息。"""
client = Ugreen.__new__(Ugreen)
client._api = Mock()
client.is_authenticated = Mock(return_value=True)
client.get_iteminfo = Mock(side_effect=[None, SimpleNamespace(tmdbid=12345)])
client._Ugreen__search_tv_item = Mock(return_value={"ug_video_info_id": "new-series-id"})
client._api.get_tv.return_value = {
"season_info": [{"category_id": "season-1", "season_num": 1}],
"tv_info": [{"category_id": "season-1", "episode": 1}],
}
item_id, episodes = client.get_tv_episodes(
item_id="old-series-id",
title="测试剧集",
year="2026",
tmdb_id=12345,
)
self.assertEqual(item_id, "new-series-id")
self.assertEqual(episodes, {1: [1]})
client._Ugreen__search_tv_item.assert_called_once_with("测试剧集", "2026", 12345)
def test_trime_media_tv_episodes_fallback_when_cached_item_id_missing(self):
"""飞牛影视缓存ID失效时应重新搜索剧集ID后再查询集信息。"""
client = TrimeMedia.__new__(TrimeMedia)
client._api = Mock()
client.is_authenticated = Mock(return_value=True)
client.get_iteminfo = Mock(side_effect=[None, SimpleNamespace(tmdbid=12345)])
client._TrimeMedia__get_series_id_by_name = Mock(return_value="new-series-id")
client._api.season_list.return_value = [SimpleNamespace(season_number=1, guid="season-1")]
client._api.episode_list.return_value = [
SimpleNamespace(season_number=1, episode_number=1)
]
item_id, episodes = client.get_tv_episodes(
item_id="old-series-id",
title="测试剧集",
year="2026",
tmdb_id=12345,
)
self.assertEqual(item_id, "new-series-id")
self.assertEqual(episodes, {1: [1]})
client._TrimeMedia__get_series_id_by_name.assert_called_once_with("测试剧集", "2026")
if __name__ == "__main__":
unittest.main()