fix(subscribe): require complete coverage for full best versions (#5857)

This commit is contained in:
InfinityPacer
2026-05-29 14:10:29 +08:00
committed by GitHub
parent 762a7fbba7
commit b45956f850
4 changed files with 288 additions and 6 deletions

View File

@@ -1,10 +1,12 @@
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock
import app.chain.download as download_module
from app.chain.download import DownloadChain
from app.core.context import Context, MediaInfo, TorrentInfo
from app.core.metainfo import MetaInfo
from app.schemas import NotExistMediaInfo
from app.schemas.types import MediaType
@@ -95,3 +97,231 @@ def test_download_single_submits_download_added_to_background(monkeypatch):
download_dir=Path("/downloads"),
torrent_content=b"torrent-content",
)
class _FakeBatchTorrentHelper:
"""
为批量下载测试提供稳定排序和种子文件集数解析。
"""
episodes = []
def sort_group_torrents(self, contexts):
return contexts
def get_torrent_episodes(self, _files):
return list(self.episodes)
def _build_tv_context(episode_list=None):
"""
构造标题未显式标集数的单季电视剧候选。
"""
episodes = episode_list or []
return SimpleNamespace(
media_info=SimpleNamespace(type=MediaType.TV, tmdb_id=1, douban_id=None),
meta_info=SimpleNamespace(
season_list=[1],
episode_list=episodes,
title="Test Show",
org_string="Test Show S01 2160p",
set_episodes=lambda begin, end: None,
),
torrent_info=SimpleNamespace(title="Test Show S01 2160p", site_name="TestSite"),
allowed_episodes=None,
)
def test_batch_download_rejects_complete_coverage_when_files_do_not_cover_target(monkeypatch):
"""
完整覆盖要求不能让 1-13 这种局部包冒充 1-143 的目标范围。
"""
_FakeBatchTorrentHelper.episodes = list(range(1, 14))
monkeypatch.setattr(download_module, "TorrentHelper", _FakeBatchTorrentHelper)
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
chain = DownloadChain.__new__(DownloadChain)
chain.download_torrent = MagicMock(return_value=(b"torrent-content", "", ["demo.mkv"]))
chain.download_single = MagicMock(return_value="hash")
context = _build_tv_context()
no_exists = {
1: {
1: NotExistMediaInfo(
season=1,
episodes=[],
total_episode=143,
start_episode=1,
require_complete_coverage=True,
)
}
}
downloads, lefts = chain.batch_download(contexts=[context], no_exists=no_exists)
assert downloads == []
assert lefts == no_exists
chain.download_single.assert_not_called()
def test_batch_download_accepts_complete_coverage_when_files_cover_target_range(monkeypatch):
"""
自定义起始集场景按目标范围覆盖判断100-143 可满足 start=100、total=143。
"""
_FakeBatchTorrentHelper.episodes = list(range(100, 144))
monkeypatch.setattr(download_module, "TorrentHelper", _FakeBatchTorrentHelper)
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
chain = DownloadChain.__new__(DownloadChain)
chain.download_torrent = MagicMock(return_value=(b"torrent-content", "", ["demo.mkv"]))
chain.download_single = MagicMock(return_value="hash")
context = _build_tv_context()
no_exists = {
1: {
1: NotExistMediaInfo(
season=1,
episodes=[],
total_episode=143,
start_episode=100,
require_complete_coverage=True,
)
}
}
downloads, lefts = chain.batch_download(contexts=[context], no_exists=no_exists)
assert downloads == [context]
assert lefts == {}
chain.download_single.assert_called_once()
def test_batch_download_accepts_complete_coverage_when_title_episodes_cover_target(monkeypatch):
"""
显式标出完整范围的候选也可满足完整覆盖任务。
"""
_FakeBatchTorrentHelper.episodes = []
monkeypatch.setattr(download_module, "TorrentHelper", _FakeBatchTorrentHelper)
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
chain = DownloadChain.__new__(DownloadChain)
chain.download_torrent = MagicMock()
chain.download_single = MagicMock(return_value="hash")
context = _build_tv_context(episode_list=list(range(1, 144)))
no_exists = {
1: {
1: NotExistMediaInfo(
season=1,
episodes=[],
total_episode=143,
start_episode=1,
require_complete_coverage=True,
)
}
}
downloads, lefts = chain.batch_download(contexts=[context], no_exists=no_exists)
assert downloads == [context]
assert lefts == {}
chain.download_torrent.assert_not_called()
chain.download_single.assert_called_once()
def test_batch_download_rejects_complete_coverage_when_title_episodes_are_partial(monkeypatch):
"""
显式标出局部范围的候选不能满足完整覆盖任务。
"""
_FakeBatchTorrentHelper.episodes = []
monkeypatch.setattr(download_module, "TorrentHelper", _FakeBatchTorrentHelper)
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
chain = DownloadChain.__new__(DownloadChain)
chain.download_torrent = MagicMock()
chain.download_single = MagicMock(return_value="hash")
context = _build_tv_context(episode_list=list(range(1, 14)))
no_exists = {
1: {
1: NotExistMediaInfo(
season=1,
episodes=[],
total_episode=143,
start_episode=1,
require_complete_coverage=True,
)
}
}
downloads, lefts = chain.batch_download(contexts=[context], no_exists=no_exists)
assert downloads == []
assert lefts == no_exists
chain.download_torrent.assert_not_called()
chain.download_single.assert_not_called()
def test_batch_download_complete_coverage_ignores_allowed_episode_narrowing(monkeypatch):
"""
完整覆盖任务不能因候选允许集裁剪而把局部包误判为覆盖目标范围。
"""
_FakeBatchTorrentHelper.episodes = []
monkeypatch.setattr(download_module, "TorrentHelper", _FakeBatchTorrentHelper)
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
chain = DownloadChain.__new__(DownloadChain)
chain.download_torrent = MagicMock()
chain.download_single = MagicMock(return_value="hash")
context = _build_tv_context(episode_list=[1, 2])
context.allowed_episodes = {1, 2}
no_exists = {
1: {
1: NotExistMediaInfo(
season=1,
episodes=[],
total_episode=12,
start_episode=1,
require_complete_coverage=True,
)
}
}
downloads, lefts = chain.batch_download(contexts=[context], no_exists=no_exists)
assert downloads == []
assert lefts == no_exists
chain.download_torrent.assert_not_called()
chain.download_single.assert_not_called()
def test_batch_download_keeps_count_check_without_complete_coverage(monkeypatch):
"""
普通整季缺失仍沿用数量判断,避免完整覆盖语义影响非严格场景。
"""
_FakeBatchTorrentHelper.episodes = list(range(2, 145))
monkeypatch.setattr(download_module, "TorrentHelper", _FakeBatchTorrentHelper)
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
chain = DownloadChain.__new__(DownloadChain)
chain.download_torrent = MagicMock(return_value=(b"torrent-content", "", ["demo.mkv"]))
chain.download_single = MagicMock(return_value="hash")
context = _build_tv_context()
no_exists = {
1: {
1: NotExistMediaInfo(
season=1,
episodes=[],
total_episode=143,
start_episode=1,
)
}
}
downloads, lefts = chain.batch_download(contexts=[context], no_exists=no_exists)
assert downloads == [context]
assert lefts == {}
chain.download_single.assert_called_once()