Files
MoviePilot/tests/test_transfer_preview.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

317 lines
9.5 KiB
Python

"""
验证整理预览对真实文件操作的隔离。
"""
from pathlib import Path
from app.domain.context import MediaInfo
from app.domain.meta.metabase import MetaBase
from app.modules.filemanager.module import FileManagerModule
from app.schemas.file import FileItem
from app.schemas.system import TransferDirectoryConf
from app.schemas.types import MediaType
class GuardedStorage:
"""
用于验证预览模式不会访问可能有副作用的存储整理接口。
"""
# 被调用即失败。
def get_folder(self, path: Path): # pragma: no cover
"""
拒绝创建或读取目标目录,检测预览阶段越界访问存储。
"""
raise AssertionError(f"预览不应创建或获取目标目录:{path}")
# 被调用即失败。
def get_item(self, path: Path): # pragma: no cover
"""
拒绝探测目标文件,保证预览只计算目标路径。
"""
raise AssertionError(f"预览不应探测目标文件:{path}")
# 被调用即失败。
def copy(self, *args, **kwargs): # pragma: no cover
"""
拒绝复制操作,防止预览产生真实文件写入。
"""
raise AssertionError("预览不应复制文件")
# 被调用即失败。
def move(self, *args, **kwargs): # pragma: no cover
"""
拒绝移动操作,防止预览改变源文件位置。
"""
raise AssertionError("预览不应移动文件")
# 被调用即失败。
def rename(self, *args, **kwargs): # pragma: no cover
"""
拒绝重命名操作,防止预览修改现有文件。
"""
raise AssertionError("预览不应重命名文件")
# 被调用即失败。
def delete(self, *args, **kwargs): # pragma: no cover
"""
拒绝删除操作,防止预览清理任何文件。
"""
raise AssertionError("预览不应删除文件")
def _build_meta(
title: str,
media_type: MediaType = MediaType.TV,
season: int = 1,
episode: int = 1,
part: str = None,
):
meta = MetaBase(title)
meta.type = media_type
meta.name = "Breaking Bad" if media_type == MediaType.TV else "Test Movie"
meta.year = "2008" if media_type == MediaType.TV else "2026"
meta.begin_season = season
meta.begin_episode = episode
meta.part = part
return meta
def test_cloud_storage_preview_only_calculates_target_path():
"""
网盘整理预览只计算目标路径,不访问或修改目标存储。
"""
fileitem = FileItem(
storage="alist",
path="/downloads/Test.Show.S01E01.mkv",
type="file",
name="Test.Show.S01E01.mkv",
basename="Test.Show.S01E01",
extension="mkv",
size=1024,
)
meta = _build_meta("Test.Show.S01E01.mkv", season=1, episode=1)
mediainfo = MediaInfo(
type=MediaType.TV,
title="Test Show",
year="2026",
tmdb_id=12345,
)
target_directory = TransferDirectoryConf(
name="cloud-library",
transfer_type="copy",
overwrite_mode="latest",
library_path="/library",
library_storage="alist",
renaming=True,
scraping=True,
notify=True,
)
guarded_storage = GuardedStorage()
module = FileManagerModule()
checkpoint = module.plan_transfer(
fileitem=fileitem,
meta=meta,
mediainfo=mediainfo,
target_directory=target_directory,
source_oper=guarded_storage,
preview=True,
)
transferinfo = module.execute_transfer_plan(
checkpoint,
meta=meta,
mediainfo=mediainfo,
source_oper=guarded_storage,
target_oper=guarded_storage,
)
assert transferinfo.success is True
assert transferinfo.need_notify is False
assert transferinfo.need_scrape is True
assert transferinfo.target_item.storage == "alist"
assert transferinfo.target_item.path.endswith(".mkv")
assert transferinfo.target_diritem.path.startswith("/library/")
assert transferinfo.file_list == [fileitem.path]
assert transferinfo.file_list_new == [transferinfo.target_item.path]
def test_local_storage_preview_skips_target_conflict_checks(tmp_path):
"""
本地整理预览跳过目标冲突探测,保持路径计算无写入副作用。
"""
source_file = tmp_path / "downloads" / "Test.Show.S01E02.mkv"
source_file.parent.mkdir(parents=True)
source_file.write_bytes(b"test video")
library_path = tmp_path / "library"
fileitem = FileItem(
storage="local",
path=source_file.as_posix(),
type="file",
name=source_file.name,
basename=source_file.stem,
extension="mkv",
size=source_file.stat().st_size,
)
meta = _build_meta(source_file.name, season=1, episode=2)
mediainfo = MediaInfo(
type=MediaType.TV,
title="Test Show",
year="2026",
tmdb_id=12345,
)
target_directory = TransferDirectoryConf(
name="local-library",
transfer_type="copy",
overwrite_mode="latest",
library_path=library_path.as_posix(),
library_storage="local",
renaming=True,
scraping=True,
notify=True,
)
guarded_storage = GuardedStorage()
module = FileManagerModule()
checkpoint = module.plan_transfer(
fileitem=fileitem,
meta=meta,
mediainfo=mediainfo,
target_directory=target_directory,
source_oper=guarded_storage,
preview=True,
)
transferinfo = module.execute_transfer_plan(
checkpoint,
meta=meta,
mediainfo=mediainfo,
source_oper=guarded_storage,
target_oper=guarded_storage,
)
assert transferinfo.success is True
assert transferinfo.need_notify is False
assert transferinfo.need_scrape is True
assert transferinfo.target_item.storage == "local"
assert transferinfo.target_item.path.startswith(library_path.as_posix())
assert transferinfo.target_item.path.endswith(".mkv")
assert transferinfo.file_list == [fileitem.path]
assert transferinfo.file_list_new == [transferinfo.target_item.path]
def _build_bluray_dir_preview(
source_name: str,
media_type: MediaType,
season: int = None,
part: str = None,
):
fileitem = FileItem(
storage="alist",
path=f"/downloads/{source_name}",
type="dir",
name=source_name,
basename=source_name,
)
meta = _build_meta(
fileitem.name,
media_type=media_type,
season=season,
episode=None,
part=part,
)
mediainfo = MediaInfo(
type=media_type,
title="Breaking Bad" if media_type == MediaType.TV else "Test Movie",
year="2008" if media_type == MediaType.TV else "2026",
tmdb_id=1396,
)
target_directory = TransferDirectoryConf(
name="cloud-library",
transfer_type="copy",
overwrite_mode="never",
library_path="/library",
library_storage="alist",
renaming=True,
scraping=True,
notify=True,
)
module = FileManagerModule()
source_oper = GuardedStorage()
target_oper = GuardedStorage()
checkpoint = module.plan_transfer(
fileitem=fileitem,
meta=meta,
mediainfo=mediainfo,
target_directory=target_directory,
source_oper=source_oper,
preview=True,
)
return module.execute_transfer_plan(
checkpoint,
meta=meta,
mediainfo=mediainfo,
source_oper=source_oper,
target_oper=target_oper,
)
def test_tv_bluray_dir_preview_preserves_disk_folder_from_meta_part():
"""
电视剧蓝光目录预览从元数据分段信息保留光盘子目录。
"""
transferinfo = _build_bluray_dir_preview(
source_name="Breaking Bad Season 2 - Disk 1",
media_type=MediaType.TV,
season=2,
part="Disk1",
)
assert transferinfo.success is True
assert transferinfo.target_item.path == "/library/Breaking Bad (2008)/Season 2/Disc 1"
assert transferinfo.target_diritem.path == transferinfo.target_item.path
assert transferinfo.file_list_new == [transferinfo.target_item.path]
def test_tv_bluray_dir_preview_preserves_disc_folder_from_source_name():
"""
元数据没有光盘编号时,从源目录名提取并保留光盘子目录。
"""
transferinfo = _build_bluray_dir_preview(
source_name="BREAKING_BAD_S01D01",
media_type=MediaType.TV,
season=1,
)
assert transferinfo.success is True
assert transferinfo.target_item.path == "/library/Breaking Bad (2008)/Season 1/Disc 1"
def test_tv_bluray_dir_preview_falls_back_to_source_name_without_disc_number():
"""
无法识别光盘编号时保留源目录名,避免丢失蓝光目录层级。
"""
source_name = "Breaking Bad Season 3 Bonus Disc"
transferinfo = _build_bluray_dir_preview(
source_name=source_name,
media_type=MediaType.TV,
season=3,
)
assert transferinfo.success is True
assert transferinfo.target_item.path == f"/library/Breaking Bad (2008)/Season 3/{source_name}"
def test_movie_bluray_dir_preview_keeps_movie_root_layout():
"""
电影蓝光目录直接落在电影根目录,不额外创建光盘层级。
"""
transferinfo = _build_bluray_dir_preview(
source_name="Test Movie Disc 1",
media_type=MediaType.MOVIE,
part="Disc1",
)
assert transferinfo.success is True
assert transferinfo.target_item.path == "/library/Test Movie (2026)"