mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-06 07:59:53 +08:00
fix: stop torrent search paging after short pages
This commit is contained in:
@@ -28,6 +28,7 @@ from app.agent.tools.factory import MoviePilotToolFactory
|
||||
from app.agent import ReplyMode
|
||||
from app.chain.search import SearchChain
|
||||
from app.core.config import settings
|
||||
from app.modules.indexer import IndexerModule
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
|
||||
@@ -66,6 +67,7 @@ class SearchChainAIRecommendTest(unittest.IsolatedAsyncioTestCase):
|
||||
chain.load_cache = lambda _filename: None
|
||||
chain.save_cache = lambda _cache, _filename: None
|
||||
chain.remove_cache = lambda _filename: None
|
||||
chain.get_search_page_size = IndexerModule.get_search_page_size
|
||||
return chain
|
||||
|
||||
async def test_start_recommend_task_restores_original_indices(self):
|
||||
@@ -170,15 +172,29 @@ class SearchChainAIRecommendTest(unittest.IsolatedAsyncioTestCase):
|
||||
with patch.object(settings, "SEARCH_RESOURCE_PAGES", "bad", create=True):
|
||||
self.assertEqual([0], SearchChain._build_search_pages(page="bad"))
|
||||
|
||||
def test_search_all_sites_requests_configured_pages(self):
|
||||
def test_search_all_sites_stops_after_short_page(self):
|
||||
"""
|
||||
验证普通站点默认按 100 条判断是否继续翻页。
|
||||
"""
|
||||
chain = self._make_chain()
|
||||
requested_pages = []
|
||||
chain.search_torrents = lambda **kwargs: requested_pages.append(kwargs["page"]) or [
|
||||
SimpleNamespace(title=f"Result Page {kwargs['page']}", description="")
|
||||
]
|
||||
|
||||
def search_torrents(**kwargs):
|
||||
"""
|
||||
模拟前两页满页、第三页不足 100 条,验证不会继续请求第四页。
|
||||
"""
|
||||
page = kwargs["page"]
|
||||
requested_pages.append(page)
|
||||
count = 100 if page in (0, 1) else 1
|
||||
return [
|
||||
SimpleNamespace(title=f"Result Page {page}-{index}", description="")
|
||||
for index in range(count)
|
||||
]
|
||||
|
||||
chain.search_torrents = search_torrents
|
||||
|
||||
with (
|
||||
patch.object(settings, "SEARCH_RESOURCE_PAGES", 3, create=True),
|
||||
patch.object(settings, "SEARCH_RESOURCE_PAGES", 4, create=True),
|
||||
patch("app.chain.search.SystemConfigOper") as system_config_oper,
|
||||
patch("app.chain.search.SitesHelper") as sites_helper,
|
||||
patch("app.chain.search.ProgressHelper") as progress_helper,
|
||||
@@ -200,7 +216,215 @@ class SearchChainAIRecommendTest(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
|
||||
self.assertEqual([0, 1, 2], sorted(requested_pages))
|
||||
self.assertEqual(3, len(results))
|
||||
self.assertEqual(201, len(results))
|
||||
|
||||
def test_search_all_sites_uses_configured_result_num_for_common_site(self):
|
||||
"""
|
||||
验证普通配置站点按 result_num 判断是否继续翻页。
|
||||
"""
|
||||
chain = self._make_chain()
|
||||
requested_pages = []
|
||||
|
||||
def search_torrents(**kwargs):
|
||||
"""
|
||||
模拟配置站点每页 50 条,第二页不足 50 条后停止。
|
||||
"""
|
||||
page = kwargs["page"]
|
||||
requested_pages.append(page)
|
||||
count = 50 if page == 0 else 49
|
||||
return [
|
||||
SimpleNamespace(title=f"Result Page {page}-{index}", description="")
|
||||
for index in range(count)
|
||||
]
|
||||
|
||||
chain.search_torrents = search_torrents
|
||||
|
||||
with (
|
||||
patch.object(settings, "SEARCH_RESOURCE_PAGES", 3, create=True),
|
||||
patch("app.chain.search.SystemConfigOper") as system_config_oper,
|
||||
patch("app.chain.search.SitesHelper") as sites_helper,
|
||||
patch("app.chain.search.ProgressHelper") as progress_helper,
|
||||
):
|
||||
system_config_oper.return_value.get.return_value = [1]
|
||||
sites_helper.return_value.get_indexers.return_value = [
|
||||
{"id": 1, "name": "测试站点", "result_num": 50}
|
||||
]
|
||||
progress_helper.return_value = SimpleNamespace(
|
||||
start=lambda: None,
|
||||
update=lambda **_kwargs: None,
|
||||
end=lambda: None,
|
||||
)
|
||||
|
||||
results = chain._SearchChain__search_all_sites(
|
||||
keyword="keyword",
|
||||
sites=None,
|
||||
page=0,
|
||||
)
|
||||
|
||||
self.assertEqual([0, 1], requested_pages)
|
||||
self.assertEqual(99, len(results))
|
||||
|
||||
def test_search_all_sites_uses_parser_page_size_for_yema(self):
|
||||
"""
|
||||
验证专用解析器按自身页容量判断,避免 Yema 的 40 条分页被误停。
|
||||
"""
|
||||
chain = self._make_chain()
|
||||
requested_pages = []
|
||||
|
||||
def search_torrents(**kwargs):
|
||||
"""
|
||||
模拟 Yema 第一页满 40 条,第二页不足 40 条后停止。
|
||||
"""
|
||||
page = kwargs["page"]
|
||||
requested_pages.append(page)
|
||||
count = 40 if page == 0 else 39
|
||||
return [
|
||||
SimpleNamespace(title=f"Result Page {page}-{index}", description="")
|
||||
for index in range(count)
|
||||
]
|
||||
|
||||
chain.search_torrents = search_torrents
|
||||
|
||||
with (
|
||||
patch.object(settings, "SEARCH_RESOURCE_PAGES", 3, create=True),
|
||||
patch("app.chain.search.SystemConfigOper") as system_config_oper,
|
||||
patch("app.chain.search.SitesHelper") as sites_helper,
|
||||
patch("app.chain.search.ProgressHelper") as progress_helper,
|
||||
):
|
||||
system_config_oper.return_value.get.return_value = [1]
|
||||
sites_helper.return_value.get_indexers.return_value = [
|
||||
{"id": 1, "name": "测试站点", "parser": "Yema"}
|
||||
]
|
||||
progress_helper.return_value = SimpleNamespace(
|
||||
start=lambda: None,
|
||||
update=lambda **_kwargs: None,
|
||||
end=lambda: None,
|
||||
)
|
||||
|
||||
results = chain._SearchChain__search_all_sites(
|
||||
keyword="keyword",
|
||||
sites=None,
|
||||
page=0,
|
||||
)
|
||||
|
||||
self.assertEqual([0, 1], requested_pages)
|
||||
self.assertEqual(79, len(results))
|
||||
|
||||
def test_indexer_module_search_page_size_uses_spider_metadata(self):
|
||||
"""
|
||||
验证站点单页容量由索引器模块统一读取,避免搜索链写死 parser 容量。
|
||||
"""
|
||||
self.assertEqual(
|
||||
40,
|
||||
IndexerModule.get_search_page_size({"parser": "Yema"}, keyword="keyword")
|
||||
)
|
||||
self.assertEqual(
|
||||
50,
|
||||
IndexerModule.get_search_page_size({"result_num": 50}, keyword="keyword")
|
||||
)
|
||||
self.assertIsNone(
|
||||
IndexerModule.get_search_page_size({"parser": "Haidan"}, keyword="keyword")
|
||||
)
|
||||
self.assertIsNone(
|
||||
IndexerModule.get_search_page_size({"parser": "TorrentLeech"}, keyword="keyword")
|
||||
)
|
||||
|
||||
async def test_async_search_all_sites_stops_after_empty_page(self):
|
||||
"""
|
||||
验证异步搜索遇到空页后停止后续翻页。
|
||||
"""
|
||||
chain = self._make_chain()
|
||||
requested_pages = []
|
||||
|
||||
async def async_search_torrents(**kwargs):
|
||||
"""
|
||||
模拟第二页为空,验证异步搜索不会继续请求后续页。
|
||||
"""
|
||||
page = kwargs["page"]
|
||||
requested_pages.append(page)
|
||||
count = 100 if page == 0 else 0
|
||||
return [
|
||||
SimpleNamespace(title=f"Result Page {page}-{index}", description="")
|
||||
for index in range(count)
|
||||
]
|
||||
|
||||
chain.async_search_torrents = async_search_torrents
|
||||
|
||||
with (
|
||||
patch.object(settings, "SEARCH_RESOURCE_PAGES", 4, create=True),
|
||||
patch("app.chain.search.SystemConfigOper") as system_config_oper,
|
||||
patch("app.chain.search.SitesHelper") as sites_helper,
|
||||
patch("app.chain.search.ProgressHelper") as progress_helper,
|
||||
):
|
||||
system_config_oper.return_value.get.return_value = [1]
|
||||
sites_helper.return_value.async_get_indexers = AsyncMock(
|
||||
return_value=[{"id": 1, "name": "测试站点"}]
|
||||
)
|
||||
progress_helper.return_value = SimpleNamespace(
|
||||
start=lambda: None,
|
||||
update=lambda **_kwargs: None,
|
||||
end=lambda: None,
|
||||
)
|
||||
|
||||
results = await chain._SearchChain__async_search_all_sites(
|
||||
keyword="keyword",
|
||||
sites=None,
|
||||
page=0,
|
||||
)
|
||||
|
||||
self.assertEqual([0, 1], requested_pages)
|
||||
self.assertEqual(100, len(results))
|
||||
|
||||
async def test_async_search_all_sites_stream_stops_after_short_page(self):
|
||||
"""
|
||||
验证渐进式搜索遇到非满页后停止后续翻页。
|
||||
"""
|
||||
chain = self._make_chain()
|
||||
requested_pages = []
|
||||
|
||||
async def async_search_torrents(**kwargs):
|
||||
"""
|
||||
模拟渐进式搜索第二页不足 100 条,验证不会继续请求第三页。
|
||||
"""
|
||||
page = kwargs["page"]
|
||||
requested_pages.append(page)
|
||||
count = 100 if page == 0 else 99
|
||||
return [
|
||||
SimpleNamespace(title=f"Result Page {page}-{index}", description="")
|
||||
for index in range(count)
|
||||
]
|
||||
|
||||
chain.async_search_torrents = async_search_torrents
|
||||
|
||||
with (
|
||||
patch.object(settings, "SEARCH_RESOURCE_PAGES", 3, create=True),
|
||||
patch("app.chain.search.SystemConfigOper") as system_config_oper,
|
||||
patch("app.chain.search.SitesHelper") as sites_helper,
|
||||
patch("app.chain.search.ProgressHelper") as progress_helper,
|
||||
):
|
||||
system_config_oper.return_value.get.return_value = [1]
|
||||
sites_helper.return_value.async_get_indexers = AsyncMock(
|
||||
return_value=[{"id": 1, "name": "测试站点"}]
|
||||
)
|
||||
progress_helper.return_value = SimpleNamespace(
|
||||
start=lambda: None,
|
||||
update=lambda **_kwargs: None,
|
||||
end=lambda: None,
|
||||
)
|
||||
|
||||
events = [
|
||||
event
|
||||
async for event in chain._SearchChain__async_search_all_sites_stream(
|
||||
keyword="keyword",
|
||||
sites=None,
|
||||
page=0,
|
||||
)
|
||||
]
|
||||
|
||||
append_events = [event for event in events if event.get("type") == "append"]
|
||||
self.assertEqual([0, 1], requested_pages)
|
||||
self.assertEqual([0, 1], [event["page"] for event in append_events])
|
||||
self.assertEqual(199, append_events[-1]["total_items"])
|
||||
|
||||
def test_search_by_id_caches_replayable_search_params_when_caching(self):
|
||||
chain = self._make_chain()
|
||||
|
||||
Reference in New Issue
Block a user