mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-09 23:44:20 +08:00
feat: 统计共享媒体识别命中次数
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
from unittest.mock import Mock
|
||||
|
||||
from app.api.endpoints import douban as douban_endpoint
|
||||
from app.db.user_oper import get_current_active_superuser_async
|
||||
from app.modules.douban.douban_cache import DoubanCache
|
||||
from app.schemas.types import MediaType
|
||||
from app.schemas.types import MediaType, SystemConfigKey
|
||||
|
||||
|
||||
class _MemoryCacheStub:
|
||||
@@ -116,7 +117,14 @@ def test_douban_cache_endpoint_returns_management_statistics(monkeypatch):
|
||||
"recognized": {"id": "1", "title": "Alpha", "type": MediaType.MOVIE},
|
||||
"unrecognized": {"id": 0},
|
||||
})
|
||||
get_system_config = Mock(return_value=None)
|
||||
monkeypatch.setattr(douban_endpoint, "DoubanCache", lambda: cache)
|
||||
monkeypatch.setattr(
|
||||
douban_endpoint,
|
||||
"SystemConfigOper",
|
||||
lambda: type("SystemConfigStub", (), {"get": get_system_config})(),
|
||||
)
|
||||
monkeypatch.setattr(douban_endpoint.settings, "MEDIA_RECOGNIZE_SHARE", False)
|
||||
|
||||
response = asyncio.run(douban_endpoint.douban_recognition_cache(None))
|
||||
|
||||
@@ -124,6 +132,11 @@ def test_douban_cache_endpoint_returns_management_statistics(monkeypatch):
|
||||
assert response.data["count"] == 2
|
||||
assert response.data["recognized"] == 1
|
||||
assert response.data["unrecognized"] == 1
|
||||
assert response.data["shared_recognized"] == 0
|
||||
assert response.data["shared_recognize_enabled"] is False
|
||||
get_system_config.assert_called_once_with(
|
||||
SystemConfigKey.MediaRecognizeShareCount
|
||||
)
|
||||
|
||||
|
||||
def test_douban_cache_delete_endpoint_reports_missing_item(monkeypatch):
|
||||
|
||||
134
tests/test_media_recognize_share_statistics.py
Normal file
134
tests/test_media_recognize_share_statistics.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import asyncio
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.core.context import MediaInfo
|
||||
from app.core.meta import MetaBase
|
||||
from app.helper.server import MoviePilotServerHelper
|
||||
from app.schemas.types import MediaType, SystemConfigKey
|
||||
|
||||
|
||||
def _build_meta(name: str) -> MetaBase:
|
||||
"""构造共享识别统计测试所需的媒体元数据。"""
|
||||
meta = MetaBase(name)
|
||||
meta.name = name
|
||||
meta.type = MediaType.UNKNOWN
|
||||
return meta
|
||||
|
||||
|
||||
def _shared_params(tmdb_id: int) -> dict:
|
||||
"""构造共享识别结果转换后的模块参数。"""
|
||||
return {
|
||||
"mtype": MediaType.MOVIE,
|
||||
"source": "themoviedb",
|
||||
"mediaid": str(tmdb_id),
|
||||
"tmdbid": tmdb_id,
|
||||
"doubanid": None,
|
||||
"bangumiid": None,
|
||||
"anilistid": None,
|
||||
}
|
||||
|
||||
|
||||
def _mock_counter(monkeypatch) -> Mock:
|
||||
"""替换系统配置持久化入口并返回递增调用桩。"""
|
||||
increment = Mock()
|
||||
monkeypatch.setattr(
|
||||
"app.chain.SystemConfigOper",
|
||||
lambda: SimpleNamespace(increment=increment),
|
||||
)
|
||||
return increment
|
||||
|
||||
|
||||
def test_sync_shared_recognize_success_increments_persisted_count(monkeypatch):
|
||||
"""同步共享识别二次识别成功后应累计一次命中。"""
|
||||
chain = object.__new__(ChainBase)
|
||||
meta = _build_meta("共享识别电影")
|
||||
media = MediaInfo(
|
||||
title="共享识别电影",
|
||||
year="2026",
|
||||
tmdb_id=101,
|
||||
type=MediaType.MOVIE,
|
||||
)
|
||||
increment = _mock_counter(monkeypatch)
|
||||
monkeypatch.setattr("app.chain.settings.MEDIA_RECOGNIZE_SHARE", True)
|
||||
monkeypatch.setattr(chain, "run_module", Mock(side_effect=[None, media]))
|
||||
monkeypatch.setattr(chain, "_update_local_recognize_cache", Mock())
|
||||
monkeypatch.setattr(
|
||||
MoviePilotServerHelper,
|
||||
"query_recognize_share",
|
||||
Mock(return_value={"type": "movie", "tmdbid": 101}),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
MoviePilotServerHelper,
|
||||
"to_recognize_params",
|
||||
Mock(return_value=_shared_params(101)),
|
||||
)
|
||||
|
||||
result = chain.recognize_media(meta=meta, cache=False)
|
||||
|
||||
assert result is media
|
||||
increment.assert_called_once_with(SystemConfigKey.MediaRecognizeShareCount)
|
||||
|
||||
|
||||
def test_sync_shared_result_without_local_match_does_not_increment(monkeypatch):
|
||||
"""共享接口返回数据但二次识别失败时不应累计命中。"""
|
||||
chain = object.__new__(ChainBase)
|
||||
meta = _build_meta("共享识别失败电影")
|
||||
increment = _mock_counter(monkeypatch)
|
||||
monkeypatch.setattr("app.chain.settings.MEDIA_RECOGNIZE_SHARE", True)
|
||||
monkeypatch.setattr(chain, "run_module", Mock(side_effect=[None, None]))
|
||||
monkeypatch.setattr(
|
||||
MoviePilotServerHelper,
|
||||
"query_recognize_share",
|
||||
Mock(return_value={"type": "movie", "tmdbid": 102}),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
MoviePilotServerHelper,
|
||||
"to_recognize_params",
|
||||
Mock(return_value=_shared_params(102)),
|
||||
)
|
||||
|
||||
result = chain.recognize_media(meta=meta, cache=False)
|
||||
|
||||
assert result is None
|
||||
increment.assert_not_called()
|
||||
|
||||
|
||||
def test_async_shared_recognize_success_increments_persisted_count(monkeypatch):
|
||||
"""异步共享识别二次识别成功后应累计一次命中。"""
|
||||
chain = object.__new__(ChainBase)
|
||||
meta = _build_meta("异步共享识别电影")
|
||||
media = MediaInfo(
|
||||
title="异步共享识别电影",
|
||||
year="2026",
|
||||
tmdb_id=103,
|
||||
type=MediaType.MOVIE,
|
||||
)
|
||||
increment = _mock_counter(monkeypatch)
|
||||
monkeypatch.setattr("app.chain.settings.MEDIA_RECOGNIZE_SHARE", True)
|
||||
monkeypatch.setattr(
|
||||
chain,
|
||||
"async_run_module",
|
||||
AsyncMock(side_effect=[None, media]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
chain,
|
||||
"_async_update_local_recognize_cache",
|
||||
AsyncMock(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
MoviePilotServerHelper,
|
||||
"async_query_recognize_share",
|
||||
AsyncMock(return_value={"type": "movie", "tmdbid": 103}),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
MoviePilotServerHelper,
|
||||
"to_recognize_params",
|
||||
Mock(return_value=_shared_params(103)),
|
||||
)
|
||||
|
||||
result = asyncio.run(chain.async_recognize_media(meta=meta, cache=False))
|
||||
|
||||
assert result is media
|
||||
increment.assert_called_once_with(SystemConfigKey.MediaRecognizeShareCount)
|
||||
51
tests/test_systemconfig_oper.py
Normal file
51
tests/test_systemconfig_oper.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
from app.schemas.types import SystemConfigKey
|
||||
|
||||
|
||||
def test_increment_serializes_concurrent_counter_updates(monkeypatch):
|
||||
"""并发递增系统计数时不应丢失更新。"""
|
||||
oper = object.__new__(SystemConfigOper)
|
||||
oper._rlock = threading.RLock()
|
||||
stored_value = {"value": 0}
|
||||
|
||||
monkeypatch.setattr(oper, "get", lambda _key: stored_value["value"])
|
||||
monkeypatch.setattr(
|
||||
oper,
|
||||
"set",
|
||||
lambda _key, value: stored_value.update(value=value),
|
||||
)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=8) as executor:
|
||||
results = list(
|
||||
executor.map(
|
||||
lambda _index: oper.increment(
|
||||
SystemConfigKey.MediaRecognizeShareCount
|
||||
),
|
||||
range(100),
|
||||
)
|
||||
)
|
||||
|
||||
assert sorted(results) == list(range(1, 101))
|
||||
assert stored_value["value"] == 100
|
||||
|
||||
|
||||
def test_increment_supports_custom_step(monkeypatch):
|
||||
"""整数系统计数应支持指定递增步长。"""
|
||||
oper = object.__new__(SystemConfigOper)
|
||||
oper._rlock = threading.RLock()
|
||||
stored_value = {"value": 4}
|
||||
|
||||
monkeypatch.setattr(oper, "get", lambda _key: stored_value["value"])
|
||||
monkeypatch.setattr(
|
||||
oper,
|
||||
"set",
|
||||
lambda _key, value: stored_value.update(value=value),
|
||||
)
|
||||
|
||||
result = oper.increment(SystemConfigKey.MediaRecognizeShareCount, step=3)
|
||||
|
||||
assert result == 7
|
||||
assert stored_value["value"] == 7
|
||||
@@ -1,10 +1,11 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
from unittest.mock import Mock
|
||||
|
||||
from app.api.endpoints import tmdb as tmdb_endpoint
|
||||
from app.db.user_oper import get_current_active_superuser_async
|
||||
from app.modules.themoviedb.tmdb_cache import TmdbCache
|
||||
from app.schemas.types import MediaType
|
||||
from app.schemas.types import MediaType, SystemConfigKey
|
||||
|
||||
|
||||
class _MemoryCacheStub:
|
||||
@@ -97,7 +98,14 @@ def test_tmdb_cache_endpoint_returns_management_statistics(monkeypatch):
|
||||
"recognized": {"id": 1, "title": "Alpha", "type": MediaType.MOVIE},
|
||||
"unrecognized": {"id": 0},
|
||||
})
|
||||
get_system_config = Mock(return_value=7)
|
||||
monkeypatch.setattr(tmdb_endpoint, "TmdbCache", lambda: cache)
|
||||
monkeypatch.setattr(
|
||||
tmdb_endpoint,
|
||||
"SystemConfigOper",
|
||||
lambda: type("SystemConfigStub", (), {"get": get_system_config})(),
|
||||
)
|
||||
monkeypatch.setattr(tmdb_endpoint.settings, "MEDIA_RECOGNIZE_SHARE", True)
|
||||
|
||||
response = asyncio.run(tmdb_endpoint.tmdb_recognition_cache(None))
|
||||
|
||||
@@ -105,6 +113,11 @@ def test_tmdb_cache_endpoint_returns_management_statistics(monkeypatch):
|
||||
assert response.data["count"] == 2
|
||||
assert response.data["recognized"] == 1
|
||||
assert response.data["unrecognized"] == 1
|
||||
assert response.data["shared_recognized"] == 7
|
||||
assert response.data["shared_recognize_enabled"] is True
|
||||
get_system_config.assert_called_once_with(
|
||||
SystemConfigKey.MediaRecognizeShareCount
|
||||
)
|
||||
|
||||
|
||||
def test_tmdb_cache_delete_endpoint_reports_missing_item(monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user