fabb880ceb
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 31s
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 / Validate - Migration (alembic) (pull_request) Successful in 1m31s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m37s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m51s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
332 lines
11 KiB
Python
332 lines
11 KiB
Python
"""Unit tests for PR #1338: 确认生成兜底增强 — user_id 查找素材.
|
|
|
|
覆盖:
|
|
- SQLAlchemyAssetRepository.find_ready_videos_by_user
|
|
- _auto_fallback_auto_material_mode 策略2 (user_id 兜底)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api"))
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "packages"))
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from packages.adapters.sqlalchemy_impl.asset_repository import SQLAlchemyAssetRepository
|
|
from packages.adapters.sqlalchemy_impl.models import Base
|
|
from packages.domain import Asset, AssetStatus
|
|
|
|
|
|
def _make_repo():
|
|
engine = create_engine("sqlite:///:memory:")
|
|
Base.metadata.create_all(engine)
|
|
session = sessionmaker(bind=engine)()
|
|
return SQLAlchemyAssetRepository(session)
|
|
|
|
|
|
class TestFindReadyVideosByUser:
|
|
"""SQLAlchemyAssetRepository.find_ready_videos_by_user 测试."""
|
|
|
|
def test_returns_ready_videos_for_user(self):
|
|
repo = _make_repo()
|
|
user_id = "user-abc-123"
|
|
v1 = Asset.create(
|
|
project_id="",
|
|
library_id="lib-1",
|
|
name="video1.mp4",
|
|
storage_key="v/v1.mp4",
|
|
mime_type="video/mp4",
|
|
status=AssetStatus.READY,
|
|
uploaded_by_user_id=user_id,
|
|
)
|
|
v2 = Asset.create(
|
|
project_id="",
|
|
library_id="lib-1",
|
|
name="video2.mp4",
|
|
storage_key="v/v2.mp4",
|
|
mime_type="video/mp4",
|
|
status=AssetStatus.READY,
|
|
uploaded_by_user_id=user_id,
|
|
)
|
|
repo.create(v1)
|
|
repo.create(v2)
|
|
result = repo.find_ready_videos_by_user(user_id)
|
|
assert len(result) == 2
|
|
assert {a.id for a in result} == {v1.id, v2.id}
|
|
|
|
def test_excludes_non_video_assets(self):
|
|
repo = _make_repo()
|
|
user_id = "user-abc-123"
|
|
video = Asset.create(
|
|
project_id="",
|
|
library_id="lib-1",
|
|
name="clip.mp4",
|
|
storage_key="v/clip.mp4",
|
|
mime_type="video/mp4",
|
|
status=AssetStatus.READY,
|
|
uploaded_by_user_id=user_id,
|
|
)
|
|
image = Asset.create(
|
|
project_id="",
|
|
library_id="lib-1",
|
|
name="photo.jpg",
|
|
storage_key="v/photo.jpg",
|
|
mime_type="image/jpeg",
|
|
status=AssetStatus.READY,
|
|
uploaded_by_user_id=user_id,
|
|
)
|
|
repo.create(video)
|
|
repo.create(image)
|
|
result = repo.find_ready_videos_by_user(user_id)
|
|
assert len(result) == 1
|
|
assert result[0].id == video.id
|
|
|
|
def test_excludes_non_ready_assets(self):
|
|
repo = _make_repo()
|
|
user_id = "user-abc-123"
|
|
ready = Asset.create(
|
|
project_id="",
|
|
library_id="lib-1",
|
|
name="ready.mp4",
|
|
storage_key="v/ready.mp4",
|
|
mime_type="video/mp4",
|
|
status=AssetStatus.READY,
|
|
uploaded_by_user_id=user_id,
|
|
)
|
|
uploading = Asset.create(
|
|
project_id="",
|
|
library_id="lib-1",
|
|
name="uploading.mp4",
|
|
storage_key="v/uploading.mp4",
|
|
mime_type="video/mp4",
|
|
status=AssetStatus.UPLOADING,
|
|
uploaded_by_user_id=user_id,
|
|
)
|
|
repo.create(ready)
|
|
repo.create(uploading)
|
|
result = repo.find_ready_videos_by_user(user_id)
|
|
assert len(result) == 1
|
|
assert result[0].id == ready.id
|
|
|
|
def test_excludes_other_users_assets(self):
|
|
repo = _make_repo()
|
|
my_video = Asset.create(
|
|
project_id="",
|
|
library_id="lib-1",
|
|
name="mine.mp4",
|
|
storage_key="v/mine.mp4",
|
|
mime_type="video/mp4",
|
|
status=AssetStatus.READY,
|
|
uploaded_by_user_id="user-A",
|
|
)
|
|
other_video = Asset.create(
|
|
project_id="",
|
|
library_id="lib-1",
|
|
name="other.mp4",
|
|
storage_key="v/other.mp4",
|
|
mime_type="video/mp4",
|
|
status=AssetStatus.READY,
|
|
uploaded_by_user_id="user-B",
|
|
)
|
|
repo.create(my_video)
|
|
repo.create(other_video)
|
|
result = repo.find_ready_videos_by_user("user-A")
|
|
assert len(result) == 1
|
|
assert result[0].id == my_video.id
|
|
|
|
def test_empty_result_for_unknown_user(self):
|
|
repo = _make_repo()
|
|
result = repo.find_ready_videos_by_user("nonexistent-user")
|
|
assert result == []
|
|
|
|
def test_respects_limit(self):
|
|
repo = _make_repo()
|
|
user_id = "user-abc-123"
|
|
for i in range(10):
|
|
asset = Asset.create(
|
|
project_id="",
|
|
library_id="lib-1",
|
|
name=f"video_{i}.mp4",
|
|
storage_key=f"v/v{i}.mp4",
|
|
mime_type="video/mp4",
|
|
status=AssetStatus.READY,
|
|
uploaded_by_user_id=user_id,
|
|
)
|
|
repo.create(asset)
|
|
result = repo.find_ready_videos_by_user(user_id, limit=3)
|
|
assert len(result) == 3
|
|
|
|
|
|
class TestAutoFallbackAutoMaterialModeUserId:
|
|
"""_auto_fallback_auto_material_mode user_id 兜底策略测试."""
|
|
|
|
def _make_plan_check(self, project_id="", template_id="tmpl-1"):
|
|
plan = MagicMock()
|
|
plan.project_id = project_id
|
|
plan.template_id = template_id
|
|
plan.config = {}
|
|
return plan
|
|
|
|
def _make_clip(self, clip_id="clip-1"):
|
|
clip = MagicMock()
|
|
clip.id = clip_id
|
|
clip.asset_id = ""
|
|
return clip
|
|
|
|
def test_skips_when_no_clips_without_asset(self):
|
|
from app.api.routes.templates_editor._fallback import (
|
|
_auto_fallback_auto_material_mode,
|
|
)
|
|
|
|
svc = MagicMock()
|
|
plan_check = self._make_plan_check()
|
|
_auto_fallback_auto_material_mode(
|
|
svc,
|
|
"plan-1",
|
|
plan_check,
|
|
[],
|
|
MagicMock(),
|
|
MagicMock(),
|
|
user_id="user-1",
|
|
)
|
|
svc.assign_asset.assert_not_called()
|
|
|
|
def test_strategy2_user_id_fallback(self):
|
|
from app.api.routes.templates_editor._fallback import (
|
|
_auto_fallback_auto_material_mode,
|
|
)
|
|
|
|
svc = MagicMock()
|
|
plan_check = self._make_plan_check(project_id="")
|
|
clip = self._make_clip("clip-1")
|
|
mock_asset = MagicMock()
|
|
mock_asset.id = "asset-from-user"
|
|
mock_asset.status = AssetStatus.READY
|
|
mock_asset.mime_type = "video/mp4"
|
|
asset_repo = MagicMock()
|
|
asset_repo.find_ready_videos_by_user.return_value = [mock_asset]
|
|
asset_library_repo = MagicMock()
|
|
_auto_fallback_auto_material_mode(
|
|
svc,
|
|
"plan-1",
|
|
plan_check,
|
|
[clip],
|
|
asset_library_repo,
|
|
asset_repo,
|
|
user_id="user-123",
|
|
)
|
|
asset_repo.find_ready_videos_by_user.assert_called_once_with("user-123")
|
|
svc.assign_asset.assert_called_once_with("clip-1", "asset-from-user")
|
|
|
|
def test_strategy1_takes_priority_over_strategy2(self):
|
|
from app.api.routes.templates_editor._fallback import (
|
|
_auto_fallback_auto_material_mode,
|
|
)
|
|
|
|
svc = MagicMock()
|
|
plan_check = self._make_plan_check(project_id="proj-1")
|
|
clip = self._make_clip("clip-1")
|
|
mock_lib = MagicMock()
|
|
mock_lib.id = "lib-video"
|
|
mock_lib.kind = MagicMock()
|
|
mock_lib.kind.value = "video"
|
|
mock_asset = MagicMock()
|
|
mock_asset.id = "asset-from-project"
|
|
mock_asset.status = "ready"
|
|
mock_asset.mime_type = "video/mp4"
|
|
asset_library_repo = MagicMock()
|
|
asset_library_repo.find_by_project.return_value = [mock_lib]
|
|
asset_repo = MagicMock()
|
|
asset_repo.find_by_library.return_value = [mock_asset]
|
|
_auto_fallback_auto_material_mode(
|
|
svc,
|
|
"plan-1",
|
|
plan_check,
|
|
[clip],
|
|
asset_library_repo,
|
|
asset_repo,
|
|
user_id="user-123",
|
|
)
|
|
asset_library_repo.find_by_project.assert_called_once_with("proj-1")
|
|
asset_repo.find_ready_videos_by_user.assert_not_called()
|
|
svc.assign_asset.assert_called_once_with("clip-1", "asset-from-project")
|
|
|
|
def test_falls_back_when_project_has_no_videos(self):
|
|
from app.api.routes.templates_editor._fallback import (
|
|
_auto_fallback_auto_material_mode,
|
|
)
|
|
|
|
svc = MagicMock()
|
|
plan_check = self._make_plan_check(project_id="proj-1")
|
|
clip = self._make_clip("clip-1")
|
|
asset_library_repo = MagicMock()
|
|
asset_library_repo.find_by_project.return_value = []
|
|
mock_asset = MagicMock()
|
|
mock_asset.id = "asset-from-user"
|
|
mock_asset.status = AssetStatus.READY
|
|
mock_asset.mime_type = "video/mp4"
|
|
asset_repo = MagicMock()
|
|
asset_repo.find_ready_videos_by_user.return_value = [mock_asset]
|
|
_auto_fallback_auto_material_mode(
|
|
svc,
|
|
"plan-1",
|
|
plan_check,
|
|
[clip],
|
|
asset_library_repo,
|
|
asset_repo,
|
|
user_id="user-123",
|
|
)
|
|
asset_repo.find_ready_videos_by_user.assert_called_once_with("user-123")
|
|
svc.assign_asset.assert_called_once_with("clip-1", "asset-from-user")
|
|
|
|
def test_no_assets_found_does_nothing(self):
|
|
from app.api.routes.templates_editor._fallback import (
|
|
_auto_fallback_auto_material_mode,
|
|
)
|
|
|
|
svc = MagicMock()
|
|
plan_check = self._make_plan_check(project_id="")
|
|
clip = self._make_clip("clip-1")
|
|
asset_repo = MagicMock()
|
|
asset_repo.find_ready_videos_by_user.return_value = []
|
|
asset_library_repo = MagicMock()
|
|
_auto_fallback_auto_material_mode(
|
|
svc,
|
|
"plan-1",
|
|
plan_check,
|
|
[clip],
|
|
asset_library_repo,
|
|
asset_repo,
|
|
user_id="user-123",
|
|
)
|
|
svc.assign_asset.assert_not_called()
|
|
|
|
def test_no_user_id_skips_strategy2(self):
|
|
from app.api.routes.templates_editor._fallback import (
|
|
_auto_fallback_auto_material_mode,
|
|
)
|
|
|
|
svc = MagicMock()
|
|
plan_check = self._make_plan_check(project_id="")
|
|
clip = self._make_clip("clip-1")
|
|
asset_repo = MagicMock()
|
|
asset_library_repo = MagicMock()
|
|
_auto_fallback_auto_material_mode(
|
|
svc,
|
|
"plan-1",
|
|
plan_check,
|
|
[clip],
|
|
asset_library_repo,
|
|
asset_repo,
|
|
user_id="",
|
|
)
|
|
asset_repo.find_ready_videos_by_user.assert_not_called()
|
|
svc.assign_asset.assert_not_called()
|