"""自动选素材逻辑单元测试 — 过滤不支持的编码格式.""" from __future__ import annotations import sys from pathlib import Path from unittest.mock import MagicMock sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api")) import pytest from packages.domain import Asset, AssetLibrary, AssetLibraryKind, AssetStatus def _make_asset( asset_id: str, *, status=AssetStatus.READY, mime_type: str = "video/mp4", codec: str | None = None, metadata: dict | None = None, width: int = 1080, height: int = 1920, created_at=None, ) -> Asset: """构造测试用 Asset 对象.""" from datetime import datetime, timezone return Asset( id=asset_id, project_id="proj-1", library_id="lib-video", name=f"{asset_id}.mp4", storage_key=f"uploads/{asset_id}.mp4", mime_type=mime_type, status=status, codec=codec, metadata=metadata or {}, width=width, height=height, created_at=created_at or datetime.now(timezone.utc), ) def _make_mock_repo(assets: list[Asset]): """构造 mock asset_repo.""" mock_repo = MagicMock() mock_repo.find_by_library.return_value = assets return mock_repo def _make_mock_lib_repo(): """构造 mock asset_library_repo,返回一个视频素材库.""" video_lib = AssetLibrary( id="lib-video", project_id="proj-1", name="视频", kind=AssetLibraryKind.VIDEO, ) mock_repo = MagicMock() mock_repo.find_by_project.return_value = [video_lib] return mock_repo class TestAutoSelectVideoAssetsCodecFilter: """自动选素材 — 编码格式不再过滤,渲染引擎统一转码.""" def test_h264_assets_selected(self): """H.264 编码素材 → 正常选中.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("a1", codec="h264"), _make_asset("a2", codec="h264"), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert len(result) == 2 assert "a1" in result assert "a2" in result def test_hevc_assets_not_filtered_by_codec_field(self): """HEVC 编码素材 → 不再被过滤,渲染引擎会统一转码.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("a1", codec="h264"), _make_asset("a2", codec="hevc"), _make_asset("a3", codec="h265"), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) # 所有编码的素材都保留,不再按编码过滤 assert len(result) == 3 assert "a1" in result assert "a2" in result assert "a3" in result def test_hevc_assets_not_filtered_by_metadata(self): """HEVC 编码素材(metadata 里有) → 不再被过滤.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("a1", codec=None, metadata={"codec": "h264"}), _make_asset("a2", codec=None, metadata={"codec": "hevc"}), _make_asset("a3", codec=None, metadata={"codec": "vp9"}), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) # 所有编码的素材都保留 assert len(result) == 3 def test_codec_field_takes_precedence_over_metadata(self): """codec 字段优先于 metadata 里的 codec.""" from app.api.routes._helpers import auto_select_video_assets # codec 字段是 h264,但 metadata 里是 hevc → 不影响,都能选中 assets = [ _make_asset("a1", codec="h264", metadata={"codec": "hevc"}), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert len(result) == 1 assert result[0] == "a1" def test_vp9_av1_assets_not_filtered(self): """VP9 / AV1 编码素材 → 不再被过滤,渲染引擎会统一转码.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("a1", codec="vp9"), _make_asset("a2", codec="av1"), _make_asset("a3", codec="h264"), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) # 所有编码的素材都保留 assert len(result) == 3 assert "a1" in result assert "a2" in result assert "a3" in result def test_missing_codec_kept(self): """codec 字段和 metadata 里都没有 → 暂时放过(兼容存量数据).""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("a1", codec=None, metadata={}), _make_asset("a2", codec="h264"), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert len(result) == 2 def test_codec_case_insensitive(self): """不同编码格式的素材都保留,不再按编码过滤.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("a1", codec="HEVC"), _make_asset("a2", codec="H264"), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) # 所有编码的素材都保留 assert len(result) == 2 assert "a1" in result assert "a2" in result def test_only_ready_assets_selected(self): """只有 READY 状态的素材才会被选中.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("a1", status=AssetStatus.READY, codec="h264"), _make_asset("a2", status=AssetStatus.PROCESSING, codec="h264"), _make_asset("a3", status=AssetStatus.ERROR, codec="h264"), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert len(result) == 1 assert result[0] == "a1" def test_only_video_mime_selected(self): """只有 video/* mime_type 的素材才会被选中.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("a1", mime_type="video/mp4", codec="h264"), _make_asset("a2", mime_type="image/jpeg"), _make_asset("a3", mime_type="audio/mpeg"), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert len(result) == 1 assert result[0] == "a1" def test_empty_project_returns_empty(self): """空 project_id → 返回空列表.""" from app.api.routes._helpers import auto_select_video_assets asset_repo = _make_mock_repo([]) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert result == [] def test_no_video_library_returns_empty(self): """没有视频素材库 → 返回空列表.""" from app.api.routes._helpers import auto_select_video_assets image_lib = AssetLibrary( id="lib-image", project_id="proj-1", name="图片", kind=AssetLibraryKind.IMAGE, ) lib_repo = MagicMock() lib_repo.find_by_project.return_value = [image_lib] asset_repo = _make_mock_repo([]) result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert result == [] def test_sorted_by_created_at_desc(self): """结果按创建时间倒序(新素材在前).""" from datetime import datetime, timedelta, timezone from app.api.routes._helpers import auto_select_video_assets now = datetime.now(timezone.utc) assets = [ _make_asset("oldest", codec="h264", created_at=now - timedelta(hours=2)), _make_asset("newest", codec="h264", created_at=now), _make_asset("middle", codec="h264", created_at=now - timedelta(hours=1)), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert result == ["newest", "middle", "oldest"] def test_landscape_videos_filtered_out(self): """横屏视频(width > height)被过滤掉.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("portrait1", codec="h264", width=1080, height=1920), _make_asset("landscape1", codec="h264", width=1920, height=1080), _make_asset("portrait2", codec="h264", width=720, height=1280), _make_asset("landscape2", codec="h264", width=1280, height=720), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert set(result) == {"portrait1", "portrait2"} assert len(result) == 2 def test_square_videos_allowed(self): """正方形视频(width == height)不被过滤.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("square", codec="h264", width=720, height=720), _make_asset("portrait", codec="h264", width=1080, height=1920), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert "square" in result assert "portrait" in result def test_unknown_size_not_filtered(self): """尺寸未知的素材不被过滤(避免误杀存量数据).""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("unknown", codec="h264", width=0, height=0), _make_asset("portrait", codec="h264", width=1080, height=1920), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert "unknown" in result assert "portrait" in result def test_metadata_size_fallback(self): """width/height字段为空时从metadata兜底读取.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset( "landscape_in_meta", codec="h264", width=0, height=0, metadata={"width": 1920, "height": 1080}, ), _make_asset( "portrait_in_meta", codec="h264", width=0, height=0, metadata={"width": 1080, "height": 1920}, ), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) assert "landscape_in_meta" not in result assert "portrait_in_meta" in result def test_only_landscape_filtered_codec_not_filtered(self): """只有横屏过滤生效,编码不再过滤.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("good", codec="h264", width=1080, height=1920), _make_asset("hevc_portrait", codec="hevc", width=1080, height=1920), _make_asset("bad_landscape", codec="h264", width=1920, height=1080), _make_asset("hevc_landscape", codec="hevc", width=1920, height=1080), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() result = auto_select_video_assets( project_id="proj-1", asset_library_repo=lib_repo, asset_repo=asset_repo, ) # 只有横屏被过滤,编码不再过滤 assert len(result) == 2 assert "good" in result assert "hevc_portrait" in result assert "bad_landscape" not in result assert "hevc_landscape" not in result