fix: handle invalid tmdb json responses

This commit is contained in:
jxxghp
2026-05-20 09:05:18 +08:00
parent 5d02550874
commit 07f51c5d94
2 changed files with 185 additions and 5 deletions

View File

@@ -124,12 +124,15 @@ def _load_tmdb_class():
TMDb = _load_tmdb_class()
TMDbException = sys.modules["app.modules.themoviedb.tmdbv3api.exceptions"].TMDbException
class _FakeResponse:
def __init__(self, payload: dict, headers: dict):
def __init__(self, payload, headers: dict, status_code: int = 200, text: str = ""):
self._payload = payload
self.headers = headers
self.status_code = status_code
self.text = text
self._lock = RLock()
def json(self):
@@ -152,6 +155,56 @@ class TmdbResponseCacheTest(TestCase):
self.assertEqual(result["headers"]["X-RateLimit-Remaining"], "39")
pickle.dumps(result)
def test_request_rejects_scalar_json_response(self):
"""
标量JSON响应不应进入TMDB响应缓存避免后续按对象解析崩溃。
"""
tmdb = TMDb()
response = _FakeResponse(payload="upstream error", headers={})
tmdb._req.get_res = lambda *args, **kwargs: response
with self.assertRaisesRegex(TMDbException, "返回数据格式异常"):
TMDb.request.__wrapped__(tmdb, "GET", "https://example.com", None, None)
def test_request_rejects_invalid_json_response(self):
"""
非JSON响应应转换为TMDbException调用方可按连接异常统一处理。
"""
class _InvalidJsonResponse:
headers = {"Content-Type": "text/html"}
status_code = 502
text = "<html>bad gateway</html>"
def json(self):
"""
模拟上游返回无法解析为JSON的响应体。
"""
raise ValueError("invalid json")
tmdb = TMDb()
tmdb._req.get_res = lambda *args, **kwargs: _InvalidJsonResponse()
with self.assertRaisesRegex(TMDbException, "不是有效JSON.*HTTP状态码502.*bad gateway"):
TMDb.request.__wrapped__(tmdb, "GET", "https://example.com", None, None)
def test_get_response_json_rejects_invalid_live_response(self):
"""
未缓存的实时响应解析失败时也应输出统一诊断信息。
"""
class _InvalidJsonResponse:
headers = {}
status_code = 200
text = ""
def json(self):
"""
模拟HTTP 200但响应体为空的情况。
"""
raise ValueError("empty")
with self.assertRaisesRegex(TMDbException, "不是有效JSON.*响应内容为空"):
TMDb._get_response_json(_InvalidJsonResponse())
def test_async_request_returns_pickleable_snapshot(self):
tmdb = TMDb()
response = _FakeResponse(
@@ -229,3 +282,40 @@ class TmdbResponseCacheTest(TestCase):
self.assertEqual(second_results[0]["media_type"], "movie")
self.assertIsNot(first_results, second_results)
self.assertIsNot(first_results[0], second_results[0])
def test_request_obj_rejects_scalar_snapshot_before_key_lookup(self):
"""
旧缓存中的标量快照不应在读取results字段时触发AttributeError。
"""
tmdb = TMDb()
snapshot = {
TMDb._RESPONSE_SNAPSHOT_MARKER: True,
"headers": {"x-ratelimit-remaining": "39", "x-ratelimit-reset": "1234567890"},
"json": "upstream error",
}
tmdb.request = lambda *args, **kwargs: snapshot
with self.assertRaisesRegex(TMDbException, "返回数据格式异常"):
tmdb._request_obj("/search/movie", key="results")
def test_async_request_obj_rejects_scalar_snapshot_before_key_lookup(self):
"""
异步对象请求读取旧标量快照时也应走统一TMDB异常路径。
"""
tmdb = TMDb()
snapshot = {
TMDb._RESPONSE_SNAPSHOT_MARKER: True,
"headers": {"x-ratelimit-remaining": "39", "x-ratelimit-reset": "1234567890"},
"json": "upstream error",
}
async def _fake_async_request(*args, **kwargs):
"""
模拟异步请求命中已缓存的异常快照。
"""
return snapshot
tmdb.async_request = _fake_async_request
with self.assertRaisesRegex(TMDbException, "返回数据格式异常"):
asyncio.run(tmdb._async_request_obj("/search/movie", key="results"))