Files
xiaoxia-saas/tests/unit/test_fallback_user_assets.py
T
CI Bot 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
style: auto-format with black + isort + prettier [skip ci-format-check]
2026-08-10 13:22:23 +00:00

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()