mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-10 07:54:14 +08:00
fix(dashboard): distinguish empty media results (#6187)
This commit is contained in:
@@ -131,14 +131,14 @@ class JellyfinUserResolutionTest(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
|
||||
def test_get_jellyfin_librarys_returns_empty_when_user_missing(self):
|
||||
def test_get_jellyfin_librarys_reports_failure_when_user_missing(self):
|
||||
client = self._build_client()
|
||||
client.user = None
|
||||
|
||||
with patch.object(jellyfin_module, "RequestUtils") as request_utils_cls:
|
||||
libraries = client._Jellyfin__get_jellyfin_librarys()
|
||||
|
||||
self.assertEqual(libraries, [])
|
||||
self.assertIsNone(libraries)
|
||||
request_utils_cls.assert_not_called()
|
||||
|
||||
def test_get_jellyfin_librarys_uses_normalized_views_url(self):
|
||||
|
||||
78
tests/test_mediaserver_dashboard_contract.py
Normal file
78
tests/test_mediaserver_dashboard_contract.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.api.endpoints.mediaserver import latest, library, playing
|
||||
from app.chain.mediaserver import MediaServerChain
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("endpoint", "chain_method", "kwargs"),
|
||||
[
|
||||
(latest, "latest", {"server": "home", "count": 20}),
|
||||
(playing, "playing", {"server": "home", "count": 12}),
|
||||
(library, "librarys", {"server": "home", "hidden": True}),
|
||||
],
|
||||
)
|
||||
def test_dashboard_media_endpoints_preserve_successful_empty_results(
|
||||
endpoint,
|
||||
chain_method,
|
||||
kwargs,
|
||||
):
|
||||
"""媒体服务器成功返回空列表时,Dashboard 接口应保留真实空结果。"""
|
||||
with patch("app.api.endpoints.mediaserver.MediaServerChain") as chain_cls:
|
||||
getattr(chain_cls.return_value, chain_method).return_value = []
|
||||
|
||||
result = endpoint(
|
||||
**kwargs,
|
||||
userinfo=SimpleNamespace(username="alice"),
|
||||
)
|
||||
|
||||
assert result == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("endpoint", "chain_method", "kwargs"),
|
||||
[
|
||||
(latest, "latest", {"server": "home", "count": 20}),
|
||||
(playing, "playing", {"server": "home", "count": 12}),
|
||||
(library, "librarys", {"server": "home", "hidden": True}),
|
||||
],
|
||||
)
|
||||
def test_dashboard_media_endpoints_report_upstream_failures(
|
||||
endpoint,
|
||||
chain_method,
|
||||
kwargs,
|
||||
):
|
||||
"""媒体服务器请求失败时,Dashboard 接口不得把 None 折叠为空列表。"""
|
||||
with patch("app.api.endpoints.mediaserver.MediaServerChain") as chain_cls:
|
||||
getattr(chain_cls.return_value, chain_method).return_value = None
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
endpoint(
|
||||
**kwargs,
|
||||
userinfo=SimpleNamespace(username="alice"),
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 502
|
||||
assert exc_info.value.detail == "媒体服务器请求失败"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("method_name", "run_method"),
|
||||
[
|
||||
("latest", "mediaserver_latest"),
|
||||
("playing", "mediaserver_playing"),
|
||||
("librarys", "mediaserver_librarys"),
|
||||
],
|
||||
)
|
||||
def test_media_server_chain_preserves_none_from_provider(method_name, run_method):
|
||||
"""媒体服务器处理链应保留提供方失败状态,交由接口层转换为明确错误。"""
|
||||
chain = MediaServerChain.__new__(MediaServerChain)
|
||||
chain.run_module = lambda method, **kwargs: None
|
||||
|
||||
result = getattr(chain, method_name)(server="home")
|
||||
|
||||
assert result is None
|
||||
110
tests/test_recommend_dashboard_contract.py
Normal file
110
tests/test_recommend_dashboard_contract.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.api.endpoints.recommend import tmdb_movies, tmdb_trending, tmdb_tvs
|
||||
from app.modules.themoviedb.tmdbapi import TmdbApi
|
||||
from app.modules.themoviedb.tmdbv3api.exceptions import TMDbException
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("endpoint", "chain_method"),
|
||||
[
|
||||
(tmdb_movies, "async_tmdb_movies"),
|
||||
(tmdb_tvs, "async_tmdb_tvs"),
|
||||
(tmdb_trending, "async_tmdb_trending"),
|
||||
],
|
||||
)
|
||||
async def test_dashboard_recommend_endpoints_preserve_successful_empty_results(
|
||||
endpoint,
|
||||
chain_method,
|
||||
):
|
||||
"""TMDB 成功返回空列表时,推荐卡片接口应保留真实空结果。"""
|
||||
with patch("app.api.endpoints.recommend.RecommendChain") as chain_cls:
|
||||
chain_mock = AsyncMock(return_value=[])
|
||||
setattr(chain_cls.return_value, chain_method, chain_mock)
|
||||
|
||||
result = await endpoint(
|
||||
page=1,
|
||||
_=SimpleNamespace(username="alice"),
|
||||
)
|
||||
|
||||
assert result == []
|
||||
assert chain_mock.await_args.kwargs["raise_exception"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("endpoint", "chain_method"),
|
||||
[
|
||||
(tmdb_movies, "async_tmdb_movies"),
|
||||
(tmdb_tvs, "async_tmdb_tvs"),
|
||||
(tmdb_trending, "async_tmdb_trending"),
|
||||
],
|
||||
)
|
||||
async def test_dashboard_recommend_endpoints_report_upstream_failures(
|
||||
endpoint,
|
||||
chain_method,
|
||||
):
|
||||
"""TMDB 请求异常时,推荐卡片接口应返回明确的网关错误。"""
|
||||
with patch("app.api.endpoints.recommend.RecommendChain") as chain_cls:
|
||||
setattr(
|
||||
chain_cls.return_value,
|
||||
chain_method,
|
||||
AsyncMock(side_effect=TMDbException("remote unavailable")),
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await endpoint(
|
||||
page=1,
|
||||
_=SimpleNamespace(username="alice"),
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 502
|
||||
assert exc_info.value.detail == "TMDB请求失败"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("method_name", "dependency_name", "dependency_method", "kwargs"),
|
||||
[
|
||||
(
|
||||
"async_discover_movies",
|
||||
"discover",
|
||||
"async_discover_movies",
|
||||
{"params": {"page": 1}},
|
||||
),
|
||||
(
|
||||
"async_discover_tvs",
|
||||
"discover",
|
||||
"async_discover_tv_shows",
|
||||
{"params": {"page": 1}},
|
||||
),
|
||||
(
|
||||
"async_discover_trending",
|
||||
"trending",
|
||||
"async_all_week",
|
||||
{"page": 1},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_tmdb_recommend_queries_only_propagate_failures_in_strict_mode(
|
||||
method_name,
|
||||
dependency_name,
|
||||
dependency_method,
|
||||
kwargs,
|
||||
):
|
||||
"""推荐 endpoint 的严格模式应保留异常,其他调用方继续沿用空列表降级。"""
|
||||
api = TmdbApi.__new__(TmdbApi)
|
||||
dependency = SimpleNamespace(
|
||||
**{dependency_method: AsyncMock(side_effect=TMDbException("remote unavailable"))}
|
||||
)
|
||||
setattr(api, dependency_name, dependency)
|
||||
|
||||
assert await getattr(api, method_name)(**kwargs) == []
|
||||
|
||||
with pytest.raises(TMDbException, match="remote unavailable"):
|
||||
await getattr(api, method_name)(**kwargs, raise_exception=True)
|
||||
Reference in New Issue
Block a user