c052e857ff
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m1s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m15s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m34s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m30s
CI/CD Pipeline / Unit Tests (push) Successful in 5m57s
CI/CD Pipeline / Integration Tests (push) Successful in 3m16s
CI/CD Pipeline / Frontend Lint (push) Successful in 40s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 50s
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 / Build Staging API Image (push) Successful in 12m4s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m29s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m25s
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 / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 56s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 5m39s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m37s
343 lines
12 KiB
Python
Executable File
343 lines
12 KiB
Python
Executable File
"""素材库 + 素材 UseCase 单元测试(P3-1 第31波)。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from packages.application.asset_libraries import (
|
|
CreateAssetLibraryCommand,
|
|
CreateAssetLibraryUseCase,
|
|
ListAssetLibrariesUseCase,
|
|
)
|
|
from packages.application.assets import (
|
|
CreateAssetCommand,
|
|
CreateAssetUseCase,
|
|
ListAssetsUseCase,
|
|
)
|
|
from packages.domain import AssetLibraryKind, AssetStatus, ClassificationStatus
|
|
|
|
# ── ListAssetLibrariesUseCase ────────────────────────────────────────────────
|
|
|
|
|
|
class TestListAssetLibrariesUseCase:
|
|
"""ListAssetLibrariesUseCase 单元测试。"""
|
|
|
|
def test_list_libraries_success(self):
|
|
"""正常列出项目素材库。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.find_by_project.return_value = ["lib1", "lib2"]
|
|
use_case = ListAssetLibrariesUseCase(mock_repo)
|
|
|
|
result = use_case.execute("proj_001")
|
|
|
|
assert result == ["lib1", "lib2"]
|
|
mock_repo.find_by_project.assert_called_once_with("proj_001")
|
|
|
|
def test_list_libraries_empty(self):
|
|
"""项目无素材库返回空列表。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.find_by_project.return_value = []
|
|
use_case = ListAssetLibrariesUseCase(mock_repo)
|
|
|
|
result = use_case.execute("proj_001")
|
|
|
|
assert result == []
|
|
|
|
def test_list_libraries_empty_project_id_raises(self):
|
|
"""空 project_id 抛 ValueError。"""
|
|
mock_repo = MagicMock()
|
|
use_case = ListAssetLibrariesUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="project_id 不能为空"):
|
|
use_case.execute("")
|
|
|
|
def test_list_libraries_whitespace_project_id_raises(self):
|
|
"""全空格 project_id 抛 ValueError。"""
|
|
mock_repo = MagicMock()
|
|
use_case = ListAssetLibrariesUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="project_id 不能为空"):
|
|
use_case.execute(" ")
|
|
|
|
def test_list_libraries_project_id_stripped(self):
|
|
"""project_id 首尾空格被清理。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.find_by_project.return_value = []
|
|
use_case = ListAssetLibrariesUseCase(mock_repo)
|
|
|
|
use_case.execute(" proj_001 ")
|
|
|
|
mock_repo.find_by_project.assert_called_once_with("proj_001")
|
|
|
|
|
|
# ── CreateAssetLibraryUseCase ────────────────────────────────────────────────
|
|
|
|
|
|
class TestCreateAssetLibraryUseCase:
|
|
"""CreateAssetLibraryUseCase 单元测试。"""
|
|
|
|
def test_create_video_library_success(self):
|
|
"""正常创建视频素材库。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateAssetLibraryUseCase(mock_repo)
|
|
command = CreateAssetLibraryCommand(
|
|
project_id="proj_001",
|
|
name="我的视频",
|
|
kind=AssetLibraryKind.VIDEO,
|
|
)
|
|
|
|
result = use_case.execute(command)
|
|
|
|
assert result.project_id == "proj_001"
|
|
assert result.name == "我的视频"
|
|
assert result.kind == AssetLibraryKind.VIDEO
|
|
mock_repo.create.assert_called_once()
|
|
|
|
def test_create_audio_library_success(self):
|
|
"""创建音频素材库。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateAssetLibraryUseCase(mock_repo)
|
|
command = CreateAssetLibraryCommand(
|
|
project_id="proj_001",
|
|
name="配乐库",
|
|
kind=AssetLibraryKind.VOICE,
|
|
)
|
|
|
|
result = use_case.execute(command)
|
|
|
|
assert result.kind == AssetLibraryKind.VOICE
|
|
assert result.asset_count == 0
|
|
assert result.total_size == 0
|
|
|
|
def test_create_image_library_success(self):
|
|
"""创建图片素材库。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateAssetLibraryUseCase(mock_repo)
|
|
command = CreateAssetLibraryCommand(
|
|
project_id="proj_001",
|
|
name="图片素材",
|
|
kind=AssetLibraryKind.IMAGE,
|
|
)
|
|
|
|
result = use_case.execute(command)
|
|
|
|
assert result.kind == AssetLibraryKind.IMAGE
|
|
|
|
def test_create_library_name_stripped(self):
|
|
"""素材库名称首尾空格被清理。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateAssetLibraryUseCase(mock_repo)
|
|
command = CreateAssetLibraryCommand(
|
|
project_id="proj_001",
|
|
name=" 我的素材库 ",
|
|
kind=AssetLibraryKind.VIDEO,
|
|
)
|
|
|
|
result = use_case.execute(command)
|
|
|
|
assert result.name == "我的素材库"
|
|
|
|
|
|
# ── ListAssetsUseCase ────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestListAssetsUseCase:
|
|
"""ListAssetsUseCase 单元测试。"""
|
|
|
|
def test_list_assets_success(self):
|
|
"""正常列出素材库素材。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.find_by_library.return_value = ["asset1", "asset2"]
|
|
use_case = ListAssetsUseCase(mock_repo)
|
|
|
|
result = use_case.execute("lib_001")
|
|
|
|
assert result == ["asset1", "asset2"]
|
|
mock_repo.find_by_library.assert_called_once_with("lib_001")
|
|
|
|
def test_list_assets_empty(self):
|
|
"""素材库为空返回空列表。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.find_by_library.return_value = []
|
|
use_case = ListAssetsUseCase(mock_repo)
|
|
|
|
result = use_case.execute("lib_001")
|
|
|
|
assert result == []
|
|
|
|
def test_list_assets_empty_library_id_raises(self):
|
|
"""空 library_id 抛 ValueError。"""
|
|
mock_repo = MagicMock()
|
|
use_case = ListAssetsUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="library_id 不能为空"):
|
|
use_case.execute("")
|
|
|
|
def test_list_assets_whitespace_library_id_raises(self):
|
|
"""全空格 library_id 抛 ValueError。"""
|
|
mock_repo = MagicMock()
|
|
use_case = ListAssetsUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="library_id 不能为空"):
|
|
use_case.execute(" ")
|
|
|
|
def test_list_assets_library_id_stripped(self):
|
|
"""library_id 首尾空格被清理。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.find_by_library.return_value = []
|
|
use_case = ListAssetsUseCase(mock_repo)
|
|
|
|
use_case.execute(" lib_001 ")
|
|
|
|
mock_repo.find_by_library.assert_called_once_with("lib_001")
|
|
|
|
|
|
# ── CreateAssetUseCase ──────────────────────────────────────────────────────
|
|
|
|
|
|
class TestCreateAssetUseCase:
|
|
"""CreateAssetUseCase 单元测试。"""
|
|
|
|
def test_create_video_asset_success(self):
|
|
"""正常创建视频素材。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateAssetUseCase(mock_repo)
|
|
command = CreateAssetCommand(
|
|
project_id="proj_001",
|
|
library_id="lib_001",
|
|
name="video.mp4",
|
|
storage_key="assets/video.mp4",
|
|
mime_type="video/mp4",
|
|
file_size=1024000,
|
|
duration=30.5,
|
|
width=1920,
|
|
height=1080,
|
|
fps=25.0,
|
|
codec="h264",
|
|
)
|
|
|
|
result = use_case.execute(command)
|
|
|
|
assert result.name == "video.mp4"
|
|
assert result.project_id == "proj_001"
|
|
assert result.library_id == "lib_001"
|
|
assert result.storage_key == "assets/video.mp4"
|
|
assert result.mime_type == "video/mp4"
|
|
assert result.file_size == 1024000
|
|
assert result.duration == 30.5
|
|
assert result.width == 1920
|
|
assert result.height == 1080
|
|
assert result.fps == 25.0
|
|
assert result.codec == "h264"
|
|
assert result.status == AssetStatus.UPLOADING
|
|
assert result.classification_status == ClassificationStatus.PENDING
|
|
mock_repo.create.assert_called_once()
|
|
|
|
def test_create_audio_asset_success(self):
|
|
"""创建音频素材。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateAssetUseCase(mock_repo)
|
|
command = CreateAssetCommand(
|
|
project_id="proj_001",
|
|
library_id="lib_002",
|
|
name="bgm.mp3",
|
|
storage_key="audio/bgm.mp3",
|
|
mime_type="audio/mpeg",
|
|
file_size=512000,
|
|
duration=180.0,
|
|
)
|
|
|
|
result = use_case.execute(command)
|
|
|
|
assert result.mime_type == "audio/mpeg"
|
|
assert result.duration == 180.0
|
|
assert result.width is None
|
|
assert result.height is None
|
|
|
|
def test_create_asset_default_status(self):
|
|
"""默认状态为 UPLOADING + PENDING。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateAssetUseCase(mock_repo)
|
|
command = CreateAssetCommand(
|
|
project_id="proj_001",
|
|
library_id="lib_001",
|
|
name="test.mp4",
|
|
storage_key="test.mp4",
|
|
mime_type="video/mp4",
|
|
)
|
|
|
|
result = use_case.execute(command)
|
|
|
|
assert result.status == AssetStatus.UPLOADING
|
|
assert result.classification_status == ClassificationStatus.PENDING
|
|
|
|
def test_create_asset_custom_status(self):
|
|
"""可以指定自定义状态。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateAssetUseCase(mock_repo)
|
|
command = CreateAssetCommand(
|
|
project_id="proj_001",
|
|
library_id="lib_001",
|
|
name="test.mp4",
|
|
storage_key="test.mp4",
|
|
mime_type="video/mp4",
|
|
status=AssetStatus.READY,
|
|
classification_status=ClassificationStatus.COMPLETED,
|
|
quality_score=85.5,
|
|
uploaded_by_user_id="user_001",
|
|
)
|
|
|
|
result = use_case.execute(command)
|
|
|
|
assert result.status == AssetStatus.READY
|
|
assert result.classification_status == ClassificationStatus.COMPLETED
|
|
assert result.quality_score == 85.5
|
|
assert result.uploaded_by_user_id == "user_001"
|
|
|
|
def test_create_asset_with_metadata(self):
|
|
"""带 metadata 创建素材。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateAssetUseCase(mock_repo)
|
|
metadata = {"location": "beijing", "camera": "sony"}
|
|
command = CreateAssetCommand(
|
|
project_id="proj_001",
|
|
library_id="lib_001",
|
|
name="test.mp4",
|
|
storage_key="test.mp4",
|
|
mime_type="video/mp4",
|
|
metadata=metadata,
|
|
)
|
|
|
|
result = use_case.execute(command)
|
|
|
|
assert result.metadata == metadata
|
|
|
|
def test_create_asset_with_thumbnail(self):
|
|
"""带缩略图创建素材。"""
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateAssetUseCase(mock_repo)
|
|
command = CreateAssetCommand(
|
|
project_id="proj_001",
|
|
library_id="lib_001",
|
|
name="test.mp4",
|
|
storage_key="test.mp4",
|
|
mime_type="video/mp4",
|
|
thumbnail_url="http://cdn.com/thumb.jpg",
|
|
)
|
|
|
|
result = use_case.execute(command)
|
|
|
|
assert result.thumbnail_url == "http://cdn.com/thumb.jpg"
|