Files
xiaoxia-saas/tests/unit/test_auto_select_assets.py
T
xiaoxia c7733a6974
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 1m6s
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been cancelled
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been cancelled
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been cancelled
CI Build & Deploy Pipeline / Build Production API Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been cancelled
CI Build & Deploy Pipeline / Deploy Production (push) Has been cancelled
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
fix(P0): 放宽ingest视频编码拦截 + 自动选素材去掉编码过滤 (#508)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-18 14:45:53 +08:00

442 lines
14 KiB
Python
Executable File

"""自动选素材逻辑单元测试 — 过滤不支持的编码格式."""
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