fix: 移除 asset_durations 的 random_preview 条件限制 (#1509)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m15s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m54s
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 / Build Staging API Image (push) Successful in 2m28s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m28s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 3m35s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 3m16s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 3m51s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m49s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 37s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 38s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m36s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 59s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m37s
AI Code Review / AI Code Review (pull_request) Successful in 4m7s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m31s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m29s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 9m5s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 8m58s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 9m22s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m37s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m59s
CI/CD Pipeline / Integration Tests (push) Successful in 2m53s
CI/CD Pipeline / Unit Tests (push) Successful in 15m5s
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 13m6s
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 / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 7s

Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
This commit was merged in pull request #1509.
This commit is contained in:
2026-08-26 18:46:37 +08:00
committed by auto-approve-bot
parent b2d2abf8b7
commit 7da62f0f82
2 changed files with 119 additions and 2 deletions
@@ -118,9 +118,9 @@ class PlanGeneratorService:
# 4. 按 editing_mode 分配素材
if asset_ids:
# 如果是随机预览模式,获取素材时长信息
# 获取素材时长信息,用于随机起始时间
asset_durations = None
if random_preview and self._asset_repo:
if self._asset_repo:
asset_durations = self._fetch_asset_durations(asset_ids)
self._distribute_assets(
clips,
+117
View File
@@ -886,3 +886,120 @@ class TestTemplateConfigPropagation:
assert clip_cfg.get("speed_ratio") == 1.2
assert clip_cfg.get("name") == "开场"
assert clip_cfg.get("custom_field") == "value"
class TestAssetDurationsAlwaysFetched:
"""验证 asset_durations 不再受 random_preview 条件限制。
修复前:asset_durations 仅在 random_preview=True 时传入 distribute_assets
修复后:只要 _asset_repo 存在,就始终获取 asset_durations
"""
def _make_service_with_asset_repo(self):
"""创建带 mock asset_repo 的 PlanGeneratorService"""
from apps.api.app.services.plan_generator_service import PlanGeneratorService
plan_repo = StubEditPlanRepository()
clip_repo = StubEditPlanClipRepository()
# mock asset_repo: 返回带 duration 的素材
asset_repo = MagicMock()
def fake_get(asset_id):
mock_asset = MagicMock()
mock_asset.duration = 30.0 # 每个素材 30 秒
return mock_asset
asset_repo.get = MagicMock(side_effect=fake_get)
with (
patch(
"apps.api.app.services.plan_generator_service.SQLAlchemyEditPlanRepository",
return_value=plan_repo,
),
patch(
"apps.api.app.services.plan_generator_service.SQLAlchemyEditPlanClipRepository",
return_value=clip_repo,
),
):
db = MagicMock()
svc = PlanGeneratorService(db, asset_repo=asset_repo)
svc._plan_repo = plan_repo
svc._clip_repo = clip_repo
return svc, asset_repo
def test_asset_durations_fetched_without_random_preview(self):
"""random_preview=False 时也应获取 asset_durations"""
svc, asset_repo = self._make_service_with_asset_repo()
template = _make_template("one_take")
clip_configs = _make_clip_configs(
template_id=template.id,
specs=[{"clip_type": ClipType.MAIN, "order": 0, "min_duration": 3.0, "max_duration": 5.0}],
)
# patch distribute_assets 以捕获传入的参数
with patch("apps.api.app.services.plan_generator_service.distribute_assets") as mock_distribute:
svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1", "a2"],
random_preview=False, # 关键:非随机预览模式
)
# 验证 asset_durations 被传入(不是 None
mock_distribute.assert_called_once()
call_kwargs = mock_distribute.call_args
asset_durations = call_kwargs.kwargs.get("asset_durations", call_kwargs[1].get("asset_durations"))
assert asset_durations is not None, "asset_durations should be fetched even when random_preview=False"
assert "a1" in asset_durations
assert "a2" in asset_durations
assert asset_durations["a1"] == 30.0
# 验证 asset_repo.get 被调用(说明 _fetch_asset_durations 执行了)
assert asset_repo.get.call_count >= 2
def test_asset_durations_fetched_with_random_preview(self):
"""random_preview=True 时仍正常获取 asset_durations(行为不变)"""
svc, asset_repo = self._make_service_with_asset_repo()
template = _make_template("one_take")
clip_configs = _make_clip_configs(
template_id=template.id,
specs=[{"clip_type": ClipType.MAIN, "order": 0, "min_duration": 3.0, "max_duration": 5.0}],
)
with patch("apps.api.app.services.plan_generator_service.distribute_assets") as mock_distribute:
svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1"],
random_preview=True,
)
call_kwargs = mock_distribute.call_args
asset_durations = call_kwargs.kwargs.get("asset_durations", call_kwargs[1].get("asset_durations"))
assert asset_durations is not None
assert "a1" in asset_durations
def test_no_asset_repo_means_no_durations(self):
"""_asset_repo 为 None 时 asset_durations 应为 None"""
svc, _, _ = _make_generator() # 默认不带 asset_repo
template = _make_template("one_take")
clip_configs = _make_clip_configs(
template_id=template.id,
specs=[{"clip_type": ClipType.MAIN, "order": 0, "min_duration": 3.0, "max_duration": 5.0}],
)
with patch("apps.api.app.services.plan_generator_service.distribute_assets") as mock_distribute:
svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1"],
)
call_kwargs = mock_distribute.call_args
asset_durations = call_kwargs.kwargs.get("asset_durations", call_kwargs[1].get("asset_durations"))
assert asset_durations is None