"""素材库 + 素材 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"