Files
MoviePilot/tests/test_transfer_sync_extra_files.py
2026-06-23 12:07:47 +08:00

359 lines
11 KiB
Python

from pathlib import Path
from types import SimpleNamespace
from app.chain.transfer import JobManager, TransferChain
from app.core.config import settings
from app.schemas import FileItem
from app.schemas.types import MediaType
class FakeMeta:
"""
构造整理链路所需的最小剧集元数据。
"""
def __init__(self, episode: int):
self.name = "Test Show"
self.title = f"Test Show S01E{episode:02d}"
self.year = "2026"
self.type = MediaType.TV
self.begin_season = 1
self.end_season = None
self.total_season = 1
self.begin_episode = episode
self.end_episode = None
self.total_episode = 1
self.part = None
@property
def episode_list(self) -> list[int]:
"""
返回当前文件覆盖的集数列表。
"""
return [self.begin_episode]
def make_transfer_chain() -> TransferChain:
"""
构造不启动后台线程的整理链实例。
"""
chain = object.__new__(TransferChain)
chain.jobview = JobManager()
chain._media_exts = settings.RMT_MEDIAEXT
chain._subtitle_exts = settings.RMT_SUBEXT
chain._audio_exts = settings.RMT_AUDIOEXT
chain._allowed_exts = (
chain._media_exts + chain._audio_exts + chain._subtitle_exts
)
chain._success_target_files = {}
chain._scrape_batches = {}
return chain
def make_fileitem(path: str) -> FileItem:
"""
根据路径构造文件项。
"""
file_path = Path(path)
return FileItem(
storage="local",
path=file_path.as_posix(),
type="file",
name=file_path.name,
basename=file_path.stem,
extension=file_path.suffix.lstrip("."),
size=1024,
)
def test_sync_extra_subtitle_inherits_matching_video_episode(monkeypatch):
"""
同名随片字幕应继承对应视频集数,避免字幕自身识别错误时全部落到第一集。
"""
chain = make_transfer_chain()
planned = []
main_ep1_fileitem = make_fileitem(
"/downloads/Test Show (2026)/Test.Show.S01E01.2026.mkv"
)
main_ep2_fileitem = make_fileitem(
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.mkv"
)
ep1_subtitle_fileitem = make_fileitem(
"/downloads/Test Show (2026)/Test.Show.S01E01.2026.zh-cn.srt"
)
ep2_subtitle_fileitem = make_fileitem(
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.zh-cn.srt"
)
parent_fileitem = FileItem(
storage="local",
path="/downloads/Test Show (2026)/",
type="dir",
name="Test Show (2026)",
)
monkeypatch.setattr(
chain,
"_TransferChain__get_trans_fileitems",
lambda fileitem, predicate: [
(main_ep1_fileitem, False),
(main_ep2_fileitem, False),
(ep1_subtitle_fileitem, False),
(ep2_subtitle_fileitem, False),
],
)
monkeypatch.setattr(chain, "_TransferChain__put_to_jobview", lambda task: True)
monkeypatch.setattr(
chain,
"_TransferChain__register_scrape_batch_task",
lambda task: None,
)
monkeypatch.setattr(
chain,
"_TransferChain__close_scrape_batch",
lambda batch_id: None,
)
def fake_handle_transfer(task, callback=None):
"""
记录实际创建的整理任务集数。
"""
planned.append((task.fileitem.path, task.meta.begin_episode))
return True, ""
def fake_meta_info_path(path, custom_words=None):
"""
模拟字幕文件自身会被误识别为第一集的场景。
"""
file_name = Path(path).name
if file_name.endswith(".mkv") and "S01E02" in file_name:
return FakeMeta(2)
return FakeMeta(1)
monkeypatch.setattr(chain, "_TransferChain__handle_transfer", fake_handle_transfer)
monkeypatch.setattr(
"app.chain.transfer.TransferHistoryOper",
lambda: SimpleNamespace(get_by_src=lambda src, storage=None: None),
)
monkeypatch.setattr(
"app.chain.transfer.DownloadHistoryOper",
lambda: SimpleNamespace(
get_by_hash=lambda download_hash: None,
get_file_by_fullpath=lambda fullpath: None,
get_files_by_savepath=lambda savepath: [],
get_by_path=lambda path: None,
),
)
monkeypatch.setattr(
"app.chain.transfer.SystemConfigOper",
lambda: SimpleNamespace(get=lambda key: None),
)
monkeypatch.setattr("app.chain.transfer.MetaInfoPath", fake_meta_info_path)
state, errmsg = TransferChain.do_transfer(
chain,
fileitem=parent_fileitem,
background=False,
sync_extra_files=True,
)
assert state is True
assert errmsg == ""
assert planned == [
(main_ep1_fileitem.path, 1),
(ep1_subtitle_fileitem.path, 1),
(main_ep2_fileitem.path, 2),
(ep2_subtitle_fileitem.path, 2),
]
def test_single_subtitle_transfer_reuses_same_name_video_episode(monkeypatch):
"""
单独整理同名字幕时应复用主视频识别结果,不受 sync_extra_files 开关影响。
"""
chain = make_transfer_chain()
planned = []
main_fileitem = make_fileitem(
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.mkv"
)
subtitle_fileitem = make_fileitem(
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.zh-cn.srt"
)
parent_fileitem = FileItem(
storage="local",
path="/downloads/Test Show (2026)/",
type="dir",
name="Test Show (2026)",
)
monkeypatch.setattr(
chain,
"_TransferChain__get_trans_fileitems",
lambda fileitem, predicate: [(subtitle_fileitem, False)],
)
monkeypatch.setattr(chain, "_TransferChain__put_to_jobview", lambda task: True)
monkeypatch.setattr(
chain,
"_TransferChain__register_scrape_batch_task",
lambda task: None,
)
monkeypatch.setattr(
chain,
"_TransferChain__close_scrape_batch",
lambda batch_id: None,
)
def fake_handle_transfer(task, callback=None):
"""
记录单独字幕整理时使用的集数。
"""
planned.append((task.fileitem.path, task.meta.begin_episode))
return True, ""
def fake_meta_info_path(path, custom_words=None):
"""
模拟字幕自身会被误识别为第一集,主视频可正确识别为第二集。
"""
file_name = Path(path).name
if file_name.endswith(".mkv"):
return FakeMeta(2)
return FakeMeta(1)
monkeypatch.setattr(chain, "_TransferChain__handle_transfer", fake_handle_transfer)
monkeypatch.setattr(
"app.chain.transfer.TransferHistoryOper",
lambda: SimpleNamespace(get_by_src=lambda src, storage=None: None),
)
monkeypatch.setattr(
"app.chain.transfer.DownloadHistoryOper",
lambda: SimpleNamespace(
get_by_hash=lambda download_hash: None,
get_file_by_fullpath=lambda fullpath: None,
get_files_by_savepath=lambda savepath: [],
get_by_path=lambda path: None,
),
)
monkeypatch.setattr(
"app.chain.transfer.SystemConfigOper",
lambda: SimpleNamespace(get=lambda key: None),
)
monkeypatch.setattr(
"app.chain.transfer.StorageChain",
lambda: SimpleNamespace(
get_parent_item=lambda fileitem: parent_fileitem,
list_files=lambda fileitem, recursion=False: [
main_fileitem,
subtitle_fileitem,
],
),
)
monkeypatch.setattr("app.chain.transfer.MetaInfoPath", fake_meta_info_path)
state, errmsg = TransferChain.do_transfer(
chain,
fileitem=subtitle_fileitem,
background=False,
sync_extra_files=False,
)
assert state is True
assert errmsg == ""
assert planned == [(subtitle_fileitem.path, 2)]
def test_single_video_transfer_lists_parent_once_for_same_name_extra(monkeypatch):
"""
单文件视频整理只读取一次父目录,并只附带同名附加文件。
"""
chain = make_transfer_chain()
planned = []
list_files_calls = []
main_fileitem = make_fileitem(
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.mkv"
)
subtitle_fileitem = make_fileitem(
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.zh-cn.srt"
)
other_subtitle_fileitem = make_fileitem(
"/downloads/Test Show (2026)/Other.Show.S01E02.2026.zh-cn.srt"
)
parent_fileitem = FileItem(
storage="local",
path="/downloads/Test Show (2026)/",
type="dir",
name="Test Show (2026)",
)
monkeypatch.setattr(
chain,
"_TransferChain__get_trans_fileitems",
lambda fileitem, predicate: [(main_fileitem, False)],
)
monkeypatch.setattr(chain, "_TransferChain__put_to_jobview", lambda task: True)
monkeypatch.setattr(
chain,
"_TransferChain__register_scrape_batch_task",
lambda task: None,
)
monkeypatch.setattr(
chain,
"_TransferChain__close_scrape_batch",
lambda batch_id: None,
)
def fake_handle_transfer(task, callback=None):
"""
记录单视频整理时实际附带的文件。
"""
planned.append(task.fileitem.path)
return True, ""
def fake_list_files(fileitem, recursion=False):
"""
记录父目录读取次数。
"""
list_files_calls.append((fileitem.path, recursion))
return [
main_fileitem,
subtitle_fileitem,
other_subtitle_fileitem,
]
monkeypatch.setattr(chain, "_TransferChain__handle_transfer", fake_handle_transfer)
monkeypatch.setattr(
"app.chain.transfer.TransferHistoryOper",
lambda: SimpleNamespace(get_by_src=lambda src, storage=None: None),
)
monkeypatch.setattr(
"app.chain.transfer.DownloadHistoryOper",
lambda: SimpleNamespace(
get_by_hash=lambda download_hash: None,
get_file_by_fullpath=lambda fullpath: None,
get_files_by_savepath=lambda savepath: [],
get_by_path=lambda path: None,
),
)
monkeypatch.setattr(
"app.chain.transfer.SystemConfigOper",
lambda: SimpleNamespace(get=lambda key: None),
)
monkeypatch.setattr(
"app.chain.transfer.StorageChain",
lambda: SimpleNamespace(
get_parent_item=lambda fileitem: parent_fileitem,
list_files=fake_list_files,
),
)
monkeypatch.setattr("app.chain.transfer.MetaInfoPath", lambda path, custom_words=None: FakeMeta(2))
state, errmsg = TransferChain.do_transfer(
chain,
fileitem=main_fileitem,
background=False,
sync_extra_files=False,
)
assert state is True
assert errmsg == ""
assert planned == [main_fileitem.path, subtitle_fileitem.path]
assert list_files_calls == [(parent_fileitem.path, False)]