500 lines
16 KiB
Python
Executable File
500 lines
16 KiB
Python
Executable File
"""Application 层零测试模块合集 — 第100波里程碑。
|
|
|
|
覆盖:
|
|
- packages/application/generated_videos.py (8个UseCase)
|
|
- packages/application/assets.py (ListAssets + CreateAsset)
|
|
- packages/application/asset_libraries.py (ListLibraries + CreateLibrary)
|
|
|
|
策略: Mock repository,测参数校验 + 委托行为
|
|
"""
|
|
|
|
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.application.generated_videos import (
|
|
GetGeneratedVideoDownloadUrlUseCase,
|
|
GetGeneratedVideoUseCase,
|
|
GetVideosByIdsUseCase,
|
|
ListGeneratedVideosByTaskUseCase,
|
|
ListGeneratedVideosPaginatedUseCase,
|
|
ListGeneratedVideosUseCase,
|
|
UpdateVideoReviewStatusUseCase,
|
|
)
|
|
from packages.domain import AssetLibraryKind, AssetStatus, ClassificationStatus, GeneratedVideo
|
|
|
|
# ── generated_videos.py ────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestListGeneratedVideosUseCase:
|
|
def test_success(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.list_by_project.return_value = [MagicMock(spec=GeneratedVideo)]
|
|
use_case = ListGeneratedVideosUseCase(mock_repo)
|
|
|
|
result = use_case.execute("proj1")
|
|
|
|
assert len(result) == 1
|
|
mock_repo.list_by_project.assert_called_once_with("proj1")
|
|
|
|
def test_strips_project_id(self):
|
|
mock_repo = MagicMock()
|
|
use_case = ListGeneratedVideosUseCase(mock_repo)
|
|
|
|
use_case.execute(" proj1 ")
|
|
|
|
mock_repo.list_by_project.assert_called_once_with("proj1")
|
|
|
|
def test_empty_project_id_raises(self):
|
|
mock_repo = MagicMock()
|
|
use_case = ListGeneratedVideosUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="project_id 不能为空"):
|
|
use_case.execute("")
|
|
|
|
def test_whitespace_project_id_raises(self):
|
|
mock_repo = MagicMock()
|
|
use_case = ListGeneratedVideosUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="project_id 不能为空"):
|
|
use_case.execute(" \t ")
|
|
|
|
|
|
class TestListGeneratedVideosPaginatedUseCase:
|
|
def test_default_params(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.list_paginated.return_value = ([], 0)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
result, total = use_case.execute()
|
|
|
|
assert total == 0
|
|
assert result == []
|
|
mock_repo.list_paginated.assert_called_once_with(
|
|
user_id=None,
|
|
project_id=None,
|
|
status=None,
|
|
review_status=None,
|
|
page=1,
|
|
page_size=20,
|
|
)
|
|
|
|
def test_page_below_1_clamps_to_1(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.list_paginated.return_value = ([], 0)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
use_case.execute(page=0)
|
|
|
|
mock_repo.list_paginated.assert_called_once()
|
|
call_kwargs = mock_repo.list_paginated.call_args.kwargs
|
|
assert call_kwargs["page"] == 1
|
|
|
|
def test_negative_page_clamps(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.list_paginated.return_value = ([], 0)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
use_case.execute(page=-5)
|
|
|
|
assert mock_repo.list_paginated.call_args.kwargs["page"] == 1
|
|
|
|
def test_page_size_zero_clamps(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.list_paginated.return_value = ([], 0)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
use_case.execute(page_size=0)
|
|
|
|
assert mock_repo.list_paginated.call_args.kwargs["page_size"] == 20
|
|
|
|
def test_page_size_over_100_clamps(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.list_paginated.return_value = ([], 0)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
use_case.execute(page_size=200)
|
|
|
|
assert mock_repo.list_paginated.call_args.kwargs["page_size"] == 20
|
|
|
|
def test_page_size_50_ok(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.list_paginated.return_value = ([], 0)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
use_case.execute(page_size=50)
|
|
|
|
assert mock_repo.list_paginated.call_args.kwargs["page_size"] == 50
|
|
|
|
def test_with_all_filters(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.list_paginated.return_value = ([], 0)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
use_case.execute(
|
|
user_id="u1",
|
|
project_id="p1",
|
|
status="completed",
|
|
review_status="approved",
|
|
page=2,
|
|
page_size=10,
|
|
)
|
|
|
|
mock_repo.list_paginated.assert_called_once_with(
|
|
user_id="u1",
|
|
project_id="p1",
|
|
status="completed",
|
|
review_status="approved",
|
|
page=2,
|
|
page_size=10,
|
|
)
|
|
|
|
|
|
class TestGetGeneratedVideoUseCase:
|
|
def test_found(self):
|
|
mock_repo = MagicMock()
|
|
expected = MagicMock(spec=GeneratedVideo)
|
|
mock_repo.get.return_value = expected
|
|
use_case = GetGeneratedVideoUseCase(mock_repo)
|
|
|
|
result = use_case.execute("vid1")
|
|
|
|
assert result == expected
|
|
mock_repo.get.assert_called_once_with("vid1")
|
|
|
|
def test_not_found(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.get.return_value = None
|
|
use_case = GetGeneratedVideoUseCase(mock_repo)
|
|
|
|
result = use_case.execute("vid1")
|
|
|
|
assert result is None
|
|
|
|
|
|
class TestListGeneratedVideosByTaskUseCase:
|
|
def test_success(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.list_by_generation_task.return_value = [MagicMock()]
|
|
use_case = ListGeneratedVideosByTaskUseCase(mock_repo)
|
|
|
|
result = use_case.execute("task1")
|
|
|
|
assert len(result) == 1
|
|
mock_repo.list_by_generation_task.assert_called_once_with("task1")
|
|
|
|
def test_strips_task_id(self):
|
|
mock_repo = MagicMock()
|
|
use_case = ListGeneratedVideosByTaskUseCase(mock_repo)
|
|
|
|
use_case.execute(" task1 ")
|
|
|
|
mock_repo.list_by_generation_task.assert_called_once_with("task1")
|
|
|
|
def test_empty_task_id_raises(self):
|
|
mock_repo = MagicMock()
|
|
use_case = ListGeneratedVideosByTaskUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="generation_task_id 不能为空"):
|
|
use_case.execute("")
|
|
|
|
|
|
class TestGetGeneratedVideoDownloadUrlUseCase:
|
|
def test_found(self):
|
|
mock_repo = MagicMock()
|
|
mock_item = MagicMock()
|
|
mock_item.file_url = "https://cdn/v.mp4"
|
|
mock_repo.get.return_value = mock_item
|
|
use_case = GetGeneratedVideoDownloadUrlUseCase(mock_repo)
|
|
|
|
result = use_case.execute("vid1")
|
|
|
|
assert result == "https://cdn/v.mp4"
|
|
|
|
def test_not_found_returns_none(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.get.return_value = None
|
|
use_case = GetGeneratedVideoDownloadUrlUseCase(mock_repo)
|
|
|
|
result = use_case.execute("vid1")
|
|
|
|
assert result is None
|
|
|
|
|
|
class TestUpdateVideoReviewStatusUseCase:
|
|
def test_pending_review(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.update_review_status.return_value = MagicMock()
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
use_case.execute("vid1", "pending_review")
|
|
|
|
mock_repo.update_review_status.assert_called_once_with("vid1", "pending_review")
|
|
|
|
def test_approved(self):
|
|
mock_repo = MagicMock()
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
use_case.execute("vid1", "approved")
|
|
|
|
mock_repo.update_review_status.assert_called_once_with("vid1", "approved")
|
|
|
|
def test_rejected(self):
|
|
mock_repo = MagicMock()
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
use_case.execute("vid1", "rejected")
|
|
|
|
mock_repo.update_review_status.assert_called_once_with("vid1", "rejected")
|
|
|
|
def test_strips_video_id(self):
|
|
mock_repo = MagicMock()
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
use_case.execute(" vid1 ", "approved")
|
|
|
|
mock_repo.update_review_status.assert_called_once_with("vid1", "approved")
|
|
|
|
def test_empty_video_id_raises(self):
|
|
mock_repo = MagicMock()
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="video_id 不能为空"):
|
|
use_case.execute("", "approved")
|
|
|
|
def test_invalid_status_raises(self):
|
|
mock_repo = MagicMock()
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="无效的 review_status"):
|
|
use_case.execute("vid1", "invalid_status")
|
|
|
|
def test_not_found_returns_none(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.update_review_status.return_value = None
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
result = use_case.execute("vid1", "approved")
|
|
|
|
assert result is None
|
|
|
|
|
|
class TestGetVideosByIdsUseCase:
|
|
def test_success(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.get_by_ids.return_value = [MagicMock(), MagicMock()]
|
|
use_case = GetVideosByIdsUseCase(mock_repo)
|
|
|
|
result = use_case.execute(["id1", "id2", "id3"])
|
|
|
|
assert len(result) == 2
|
|
mock_repo.get_by_ids.assert_called_once_with(["id1", "id2", "id3"])
|
|
|
|
def test_empty_list(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.get_by_ids.return_value = []
|
|
use_case = GetVideosByIdsUseCase(mock_repo)
|
|
|
|
result = use_case.execute([])
|
|
|
|
assert result == []
|
|
mock_repo.get_by_ids.assert_called_once_with([])
|
|
|
|
|
|
# ── assets.py ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestCreateAssetCommand:
|
|
def test_minimal(self):
|
|
cmd = CreateAssetCommand(
|
|
project_id="p1",
|
|
library_id="l1",
|
|
name="test.mp4",
|
|
storage_key="k",
|
|
mime_type="video/mp4",
|
|
)
|
|
assert cmd.project_id == "p1"
|
|
assert cmd.library_id == "l1"
|
|
assert cmd.name == "test.mp4"
|
|
assert cmd.storage_key == "k"
|
|
assert cmd.mime_type == "video/mp4"
|
|
assert cmd.file_size == 0
|
|
assert cmd.status == AssetStatus.UPLOADING
|
|
assert cmd.classification_status == ClassificationStatus.PENDING
|
|
|
|
def test_full(self):
|
|
cmd = CreateAssetCommand(
|
|
project_id="p1",
|
|
library_id="l1",
|
|
name="test.mp4",
|
|
storage_key="k",
|
|
mime_type="video/mp4",
|
|
metadata={"k": "v"},
|
|
file_size=1024,
|
|
duration=10.0,
|
|
width=1920,
|
|
height=1080,
|
|
fps=30.0,
|
|
codec="h264",
|
|
status=AssetStatus.READY,
|
|
quality_score=0.9,
|
|
uploaded_by_user_id="u1",
|
|
)
|
|
assert cmd.file_size == 1024
|
|
assert cmd.duration == 10.0
|
|
assert cmd.status == AssetStatus.READY
|
|
assert cmd.quality_score == 0.9
|
|
|
|
|
|
class TestListAssetsUseCase:
|
|
def test_success(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.find_by_library.return_value = []
|
|
use_case = ListAssetsUseCase(mock_repo)
|
|
|
|
result = use_case.execute("lib1")
|
|
|
|
assert result == []
|
|
mock_repo.find_by_library.assert_called_once_with("lib1")
|
|
|
|
def test_strips_library_id(self):
|
|
mock_repo = MagicMock()
|
|
use_case = ListAssetsUseCase(mock_repo)
|
|
|
|
use_case.execute(" lib1 ")
|
|
|
|
mock_repo.find_by_library.assert_called_once_with("lib1")
|
|
|
|
def test_empty_library_id_raises(self):
|
|
mock_repo = MagicMock()
|
|
use_case = ListAssetsUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="library_id 不能为空"):
|
|
use_case.execute("")
|
|
|
|
|
|
class TestCreateAssetUseCase:
|
|
def test_creates_asset_via_repo(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.return_value = MagicMock()
|
|
use_case = CreateAssetUseCase(mock_repo)
|
|
|
|
cmd = CreateAssetCommand(
|
|
project_id="p1",
|
|
library_id="l1",
|
|
name="test.mp4",
|
|
storage_key="videos/t.mp4",
|
|
mime_type="video/mp4",
|
|
file_size=1024,
|
|
)
|
|
result = use_case.execute(cmd)
|
|
|
|
assert result is not None
|
|
mock_repo.create.assert_called_once()
|
|
created_asset = mock_repo.create.call_args[0][0]
|
|
assert created_asset.project_id == "p1"
|
|
assert created_asset.name == "test.mp4"
|
|
assert created_asset.file_size == 1024
|
|
assert created_asset.status == AssetStatus.UPLOADING
|
|
|
|
def test_asset_create_validation_propagates(self):
|
|
mock_repo = MagicMock()
|
|
use_case = CreateAssetUseCase(mock_repo)
|
|
|
|
cmd = CreateAssetCommand(
|
|
project_id="p1",
|
|
library_id="l1",
|
|
name="",
|
|
storage_key="k",
|
|
mime_type="video/mp4",
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="素材名称不能为空"):
|
|
use_case.execute(cmd)
|
|
|
|
|
|
# ── asset_libraries.py ────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestCreateAssetLibraryCommand:
|
|
def test_creation(self):
|
|
cmd = CreateAssetLibraryCommand(
|
|
project_id="p1",
|
|
name="我的库",
|
|
kind=AssetLibraryKind.VIDEO,
|
|
)
|
|
assert cmd.project_id == "p1"
|
|
assert cmd.name == "我的库"
|
|
assert cmd.kind == AssetLibraryKind.VIDEO
|
|
|
|
|
|
class TestListAssetLibrariesUseCase:
|
|
def test_success(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.find_by_project.return_value = []
|
|
use_case = ListAssetLibrariesUseCase(mock_repo)
|
|
|
|
result = use_case.execute("p1")
|
|
|
|
assert result == []
|
|
mock_repo.find_by_project.assert_called_once_with("p1")
|
|
|
|
def test_strips_project_id(self):
|
|
mock_repo = MagicMock()
|
|
use_case = ListAssetLibrariesUseCase(mock_repo)
|
|
|
|
use_case.execute(" p1 ")
|
|
|
|
mock_repo.find_by_project.assert_called_once_with("p1")
|
|
|
|
def test_empty_project_id_raises(self):
|
|
mock_repo = MagicMock()
|
|
use_case = ListAssetLibrariesUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="project_id 不能为空"):
|
|
use_case.execute("")
|
|
|
|
|
|
class TestCreateAssetLibraryUseCase:
|
|
def test_creates_library_via_repo(self):
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.return_value = MagicMock()
|
|
use_case = CreateAssetLibraryUseCase(mock_repo)
|
|
|
|
cmd = CreateAssetLibraryCommand(
|
|
project_id="p1",
|
|
name="视频库",
|
|
kind=AssetLibraryKind.VIDEO,
|
|
)
|
|
result = use_case.execute(cmd)
|
|
|
|
assert result is not None
|
|
mock_repo.create.assert_called_once()
|
|
created = mock_repo.create.call_args[0][0]
|
|
assert created.project_id == "p1"
|
|
assert created.name == "视频库"
|
|
assert created.kind == AssetLibraryKind.VIDEO
|
|
|
|
def test_validation_propagates(self):
|
|
mock_repo = MagicMock()
|
|
use_case = CreateAssetLibraryUseCase(mock_repo)
|
|
|
|
cmd = CreateAssetLibraryCommand(
|
|
project_id="p1",
|
|
name="",
|
|
kind=AssetLibraryKind.VIDEO,
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="素材库名称不能为空"):
|
|
use_case.execute(cmd)
|