Files
xiaoxia-saas/tests/unit/test_1970_micro_transform_render.py
T
xiaoxia fbd89b4089
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m44s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m37s
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 2m34s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 26s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m39s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 4m7s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 4m43s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m58s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 11m18s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 11m37s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
feat: #1970 hflip 按 atom_clip ai_tags.has_text 放开 + 清理重复 081 migration
- render_adapter 按非 audio 源片段顺序批量查 atom_clip.ai_tags,
  仅 has_text 显式 false 标记无文字,其余(未打标签/true/null/查询失败)保守不翻转
- UnifiedRenderService 新增 clip_has_text 注入,None 维持 P1 全保守语义
- 删除残留 081_atom_clip_ai_tags.py(与 GPU PR 的 081 撞号,内容已由 082 承载),
  develop alembic 恢复单 head:080→081_add_gpu_lipsync→082_atom_clip_ai_tags
- 新增 19 个测试(纯函数混合标记/服务门控/适配器解析/失败回退),全量 15819 passed
2026-09-19 01:06:46 +08:00

199 lines
7.3 KiB
Python

"""#1970 PR2 渲染服务微变换注入测试。
不做真实渲染,只验证 UnifiedRenderService 上微变换计划的开关、缓存、
滤镜注入与速度因子;纯参数生成在 test_1970_micro_transform_pure 覆盖。
"""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock
import pytest
def _make_service(plan_config: dict | None = None, clips=None):
from video_processing.unified_render_service import UnifiedRenderService
svc = object.__new__(UnifiedRenderService)
svc.plan = MagicMock()
svc.plan.config = plan_config or {}
svc.plan.id = "plan-1"
svc.plan.clips = clips or []
svc._micro_plan_cache = None
svc._micro_plan_loaded = False
svc._clip_has_text = None
return svc
class TestDedupGate:
def test_default_enabled_when_config_missing(self):
svc = _make_service({})
assert svc._dedup_enabled() is True
def test_explicit_true(self):
svc = _make_service({"dedup_enabled": True})
assert svc._dedup_enabled() is True
def test_explicit_false(self):
svc = _make_service({"dedup_enabled": False})
assert svc._dedup_enabled() is False
def test_plan_none_config_treated_enabled(self):
svc = _make_service(None)
svc.plan.config = None
assert svc._dedup_enabled() is True
class TestPlanBuild:
def test_disabled_returns_none_and_cached(self):
svc = _make_service({"dedup_enabled": False, "generation_task_id": "t1"})
assert svc._get_micro_transform_plan(5) is None
# 第二次走缓存
svc._dedup_enabled = MagicMock(side_effect=AssertionError("不应再次计算"))
assert svc._get_micro_transform_plan(5) is None
def test_zero_clips_returns_none(self):
svc = _make_service({"generation_task_id": "t1"})
assert svc._get_micro_transform_plan(0) is None
def test_enabled_builds_reproducible_plan(self):
cfg = {"generation_task_id": "task-abc", "video_index": 2, "bgm": {"enabled": True}}
svc1 = _make_service(cfg)
svc2 = _make_service(dict(cfg))
p1 = svc1._get_micro_transform_plan(6)
p2 = svc2._get_micro_transform_plan(6)
assert p1 is not None and p2 is not None
assert [c.speed for c in p1.clips] == [c.speed for c in p2.clips]
assert p1.seed == p2.seed
assert len(p1.clips) == 6
def test_p1_conservative_no_hflip(self):
svc = _make_service({"generation_task_id": "t1"})
plan = svc._get_micro_transform_plan(30)
assert all(not c.hflip for c in plan.clips)
def test_no_bgm_config_zero_offset(self):
svc = _make_service({"generation_task_id": "t1"})
plan = svc._get_micro_transform_plan(3)
assert plan.bgm_start_offset == 0.0
def test_bgm_enabled_offset_in_range(self):
svc = _make_service({"generation_task_id": "t1", "bgm": {"enabled": True}})
plan = svc._get_micro_transform_plan(3)
assert 2.0 <= plan.bgm_start_offset <= 8.0
class TestFilterInjection:
def test_none_mt_noop(self):
svc = _make_service({})
from video_processing.unified_render_service import UnifiedRenderService
filters = ["scale=100:100"]
UnifiedRenderService._apply_micro_transform_video(filters, None)
UnifiedRenderService._apply_micro_hflip(filters, None)
assert filters == ["scale=100:100"]
def test_eq_injection(self):
svc = _make_service({"generation_task_id": "t1"})
plan = svc._get_micro_transform_plan(1)
mt = plan.clips[0]
from video_processing.unified_render_service import UnifiedRenderService
filters: list[str] = []
UnifiedRenderService._apply_micro_transform_video(filters, mt)
assert filters and filters[0].startswith("eq=brightness=")
assert "contrast=" in filters[0] and "saturation=" in filters[0]
def test_hflip_skipped_p1(self):
svc = _make_service({"generation_task_id": "t1"})
plan = svc._get_micro_transform_plan(10)
from video_processing.unified_render_service import UnifiedRenderService
for mt in plan.clips:
filters: list[str] = []
UnifiedRenderService._apply_micro_hflip(filters, mt)
assert filters == []
def test_speed_factor(self):
from video_processing.micro_transform_pure import ClipMicroTransform
from video_processing.unified_render_service import UnifiedRenderService
assert UnifiedRenderService._micro_speed_factor(None) == 1.0
assert UnifiedRenderService._micro_speed_factor(ClipMicroTransform(0, speed=1.025)) == pytest.approx(1.025)
assert UnifiedRenderService._micro_speed_factor(MagicMock(speed=0.97)) == pytest.approx(0.97)
def test_bgm_offset_reader_respects_flag(self):
svc_off = _make_service({"dedup_enabled": False})
assert svc_off._get_micro_bgm_offset() == 0.0
svc_on = _make_service({"generation_task_id": "t1", "bgm": {"enabled": True}}, clips=[MagicMock()])
off = svc_on._get_micro_bgm_offset()
assert 2.0 <= off <= 8.0
def test_bgm_offset_zero_without_bgm(self):
svc = _make_service({"generation_task_id": "t1"}, clips=[MagicMock()])
assert svc._get_micro_bgm_offset() == 0.0
class TestStreamCopyGate:
"""dedup 开启时微变换需要重编码,stream copy 必须被拒绝。"""
def _build(self, config):
from types import SimpleNamespace
from unittest.mock import patch
from video_processing.unified_render_service import ResolvedClip, UnifiedRenderService
source = SimpleNamespace(
id="c1",
clip_type="main",
)
svc = object.__new__(UnifiedRenderService)
svc.output_width = 1280
svc.output_height = 720
svc.output_fps = 25
svc.plan = MagicMock()
svc.plan.id = "plan-1"
svc.plan.config = config
svc.plan.clips = [source]
svc.clips = [source]
svc._micro_plan_cache = None
svc._micro_plan_loaded = False
svc._clip_has_text = None
resolved = ResolvedClip(
clip_id="c1",
asset_id="a1",
local_path=Path("/tmp/a1.mp4"),
clip_type="main",
order=0,
)
info = {
"width": 1280,
"height": 720,
"fps": 25.0,
"video_codec": "h264",
"pix_fmt": "yuv420p",
"duration": 5.0,
"has_audio": True,
"audio_codec": "aac",
}
return svc, resolved, info
def test_dedup_enabled_blocks_stream_copy(self):
from unittest.mock import patch
svc, resolved, info = self._build({"dedup_enabled": True, "generation_task_id": "t1"})
with patch("video_processing.unified_render_service.probe_video_info", return_value=info):
can_copy, reason = svc._can_use_stream_copy(resolved)
assert can_copy is False
assert "微变换" in reason
def test_dedup_disabled_allows_stream_copy(self):
from unittest.mock import patch
svc, resolved, info = self._build({"dedup_enabled": False})
with patch("video_processing.unified_render_service.probe_video_info", return_value=info):
can_copy, _ = svc._can_use_stream_copy(resolved)
assert can_copy is True