2ee110f19c
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
320 lines
10 KiB
Python
Executable File
320 lines
10 KiB
Python
Executable File
"""生成视频 UseCase 单元测试."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from packages.application.generated_videos import (
|
|
GetGeneratedVideoDownloadUrlUseCase,
|
|
GetGeneratedVideoUseCase,
|
|
GetVideosByIdsUseCase,
|
|
ListGeneratedVideosByTaskUseCase,
|
|
ListGeneratedVideosPaginatedUseCase,
|
|
ListGeneratedVideosUseCase,
|
|
UpdateVideoReviewStatusUseCase,
|
|
)
|
|
from packages.domain.generated_video import GeneratedVideo
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_repo():
|
|
return MagicMock()
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_video():
|
|
v = GeneratedVideo.create(
|
|
project_id="proj_1",
|
|
generation_task_id="task_1",
|
|
name="测试视频",
|
|
file_url="https://oss.example.com/videos/test.mp4",
|
|
user_id="user_1",
|
|
file_size=1024000,
|
|
duration=30.5,
|
|
width=1920,
|
|
height=1080,
|
|
fps=30.0,
|
|
)
|
|
v.id = "video_123"
|
|
return v
|
|
|
|
|
|
class TestListGeneratedVideosUseCase:
|
|
"""ListGeneratedVideosUseCase 测试"""
|
|
|
|
def test_list_returns_results(self, mock_repo, sample_video):
|
|
"""正常返回项目生成视频列表"""
|
|
mock_repo.list_by_project.return_value = [sample_video]
|
|
use_case = ListGeneratedVideosUseCase(mock_repo)
|
|
|
|
result = use_case.execute("proj_1")
|
|
|
|
assert len(result) == 1
|
|
assert result[0].id == "video_123"
|
|
mock_repo.list_by_project.assert_called_once_with("proj_1")
|
|
|
|
def test_empty_project_id_raises(self, mock_repo):
|
|
"""空 project_id 抛出"""
|
|
use_case = ListGeneratedVideosUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="project_id 不能为空"):
|
|
use_case.execute("")
|
|
|
|
mock_repo.list_by_project.assert_not_called()
|
|
|
|
def test_whitespace_project_id_raises(self, mock_repo):
|
|
"""纯空格 project_id 抛出"""
|
|
use_case = ListGeneratedVideosUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="project_id 不能为空"):
|
|
use_case.execute(" ")
|
|
|
|
def test_project_id_stripped(self, mock_repo, sample_video):
|
|
"""project_id 会被 strip"""
|
|
mock_repo.list_by_project.return_value = [sample_video]
|
|
use_case = ListGeneratedVideosUseCase(mock_repo)
|
|
|
|
use_case.execute(" proj_1 ")
|
|
|
|
mock_repo.list_by_project.assert_called_once_with("proj_1")
|
|
|
|
|
|
class TestListGeneratedVideosPaginatedUseCase:
|
|
"""ListGeneratedVideosPaginatedUseCase 测试"""
|
|
|
|
def test_paginated_default_params(self, mock_repo, sample_video):
|
|
"""默认分页参数正确传递"""
|
|
mock_repo.list_paginated.return_value = ([sample_video], 1)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
results, total = use_case.execute(user_id="user_1")
|
|
|
|
assert len(results) == 1
|
|
assert total == 1
|
|
mock_repo.list_paginated.assert_called_once_with(
|
|
user_id="user_1",
|
|
project_id=None,
|
|
status=None,
|
|
review_status=None,
|
|
page=1,
|
|
page_size=20,
|
|
)
|
|
|
|
def test_page_less_than_1_clamped(self, mock_repo, sample_video):
|
|
"""page < 1 被修正为 1"""
|
|
mock_repo.list_paginated.return_value = ([sample_video], 1)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
use_case.execute(page=0)
|
|
|
|
call_kwargs = mock_repo.list_paginated.call_args[1]
|
|
assert call_kwargs["page"] == 1
|
|
|
|
def test_page_size_less_than_1_clamped(self, mock_repo, sample_video):
|
|
"""page_size < 1 被修正为 20"""
|
|
mock_repo.list_paginated.return_value = ([sample_video], 1)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
use_case.execute(page_size=0)
|
|
|
|
call_kwargs = mock_repo.list_paginated.call_args[1]
|
|
assert call_kwargs["page_size"] == 20
|
|
|
|
def test_page_size_greater_than_100_clamped(self, mock_repo, sample_video):
|
|
"""page_size > 100 被修正为 20"""
|
|
mock_repo.list_paginated.return_value = ([sample_video], 1)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
use_case.execute(page_size=200)
|
|
|
|
call_kwargs = mock_repo.list_paginated.call_args[1]
|
|
assert call_kwargs["page_size"] == 20
|
|
|
|
def test_full_filter_params(self, mock_repo, sample_video):
|
|
"""所有过滤参数正确传递"""
|
|
mock_repo.list_paginated.return_value = ([sample_video], 1)
|
|
use_case = ListGeneratedVideosPaginatedUseCase(mock_repo)
|
|
|
|
use_case.execute(
|
|
user_id="user_1",
|
|
project_id="proj_1",
|
|
status="completed",
|
|
review_status="approved",
|
|
page=2,
|
|
page_size=50,
|
|
)
|
|
|
|
mock_repo.list_paginated.assert_called_once_with(
|
|
user_id="user_1",
|
|
project_id="proj_1",
|
|
status="completed",
|
|
review_status="approved",
|
|
page=2,
|
|
page_size=50,
|
|
)
|
|
|
|
|
|
class TestGetGeneratedVideoUseCase:
|
|
"""GetGeneratedVideoUseCase 测试"""
|
|
|
|
def test_get_existing(self, mock_repo, sample_video):
|
|
"""获取存在的视频"""
|
|
mock_repo.get.return_value = sample_video
|
|
use_case = GetGeneratedVideoUseCase(mock_repo)
|
|
|
|
result = use_case.execute("video_123")
|
|
|
|
assert result is not None
|
|
assert result.id == "video_123"
|
|
|
|
def test_get_nonexistent_returns_none(self, mock_repo):
|
|
"""获取不存在的视频返回 None"""
|
|
mock_repo.get.return_value = None
|
|
use_case = GetGeneratedVideoUseCase(mock_repo)
|
|
|
|
result = use_case.execute("nonexistent")
|
|
|
|
assert result is None
|
|
|
|
|
|
class TestListGeneratedVideosByTaskUseCase:
|
|
"""ListGeneratedVideosByTaskUseCase 测试"""
|
|
|
|
def test_list_by_task(self, mock_repo, sample_video):
|
|
"""按任务ID查询视频"""
|
|
mock_repo.list_by_generation_task.return_value = [sample_video]
|
|
use_case = ListGeneratedVideosByTaskUseCase(mock_repo)
|
|
|
|
result = use_case.execute("task_1")
|
|
|
|
assert len(result) == 1
|
|
mock_repo.list_by_generation_task.assert_called_once_with("task_1")
|
|
|
|
def test_empty_task_id_raises(self, mock_repo):
|
|
"""空任务ID抛出"""
|
|
use_case = ListGeneratedVideosByTaskUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="generation_task_id 不能为空"):
|
|
use_case.execute("")
|
|
|
|
def test_task_id_stripped(self, mock_repo, sample_video):
|
|
"""任务ID被 strip"""
|
|
mock_repo.list_by_generation_task.return_value = [sample_video]
|
|
use_case = ListGeneratedVideosByTaskUseCase(mock_repo)
|
|
|
|
use_case.execute(" task_1 ")
|
|
|
|
mock_repo.list_by_generation_task.assert_called_once_with("task_1")
|
|
|
|
|
|
class TestGetGeneratedVideoDownloadUrlUseCase:
|
|
"""GetGeneratedVideoDownloadUrlUseCase 测试"""
|
|
|
|
def test_get_url_success(self, mock_repo, sample_video):
|
|
"""成功获取下载URL"""
|
|
mock_repo.get.return_value = sample_video
|
|
use_case = GetGeneratedVideoDownloadUrlUseCase(mock_repo)
|
|
|
|
result = use_case.execute("video_123")
|
|
|
|
assert result == sample_video.file_url
|
|
assert "test.mp4" in result
|
|
|
|
def test_get_url_nonexistent_returns_none(self, mock_repo):
|
|
"""视频不存在返回 None"""
|
|
mock_repo.get.return_value = None
|
|
use_case = GetGeneratedVideoDownloadUrlUseCase(mock_repo)
|
|
|
|
result = use_case.execute("nonexistent")
|
|
|
|
assert result is None
|
|
|
|
|
|
class TestUpdateVideoReviewStatusUseCase:
|
|
"""UpdateVideoReviewStatusUseCase 测试"""
|
|
|
|
def test_update_status_pending_review(self, mock_repo, sample_video):
|
|
"""更新为待审核"""
|
|
mock_repo.update_review_status.return_value = sample_video
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
result = use_case.execute("video_123", "pending_review")
|
|
|
|
assert result is not None
|
|
mock_repo.update_review_status.assert_called_once_with("video_123", "pending_review")
|
|
|
|
def test_update_status_approved(self, mock_repo, sample_video):
|
|
"""更新为审核通过"""
|
|
mock_repo.update_review_status.return_value = sample_video
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
use_case.execute("video_123", "approved")
|
|
|
|
mock_repo.update_review_status.assert_called_once_with("video_123", "approved")
|
|
|
|
def test_update_status_rejected(self, mock_repo, sample_video):
|
|
"""更新为审核拒绝"""
|
|
mock_repo.update_review_status.return_value = sample_video
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
use_case.execute("video_123", "rejected")
|
|
|
|
mock_repo.update_review_status.assert_called_once_with("video_123", "rejected")
|
|
|
|
def test_invalid_status_raises(self, mock_repo):
|
|
"""无效状态抛出 ValueError"""
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="无效的 review_status"):
|
|
use_case.execute("video_123", "invalid_status")
|
|
|
|
mock_repo.update_review_status.assert_not_called()
|
|
|
|
def test_empty_video_id_raises(self, mock_repo):
|
|
"""空视频ID抛出"""
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
with pytest.raises(ValueError, match="video_id 不能为空"):
|
|
use_case.execute("", "approved")
|
|
|
|
def test_video_id_stripped(self, mock_repo, sample_video):
|
|
"""视频ID被 strip"""
|
|
mock_repo.update_review_status.return_value = sample_video
|
|
use_case = UpdateVideoReviewStatusUseCase(mock_repo)
|
|
|
|
use_case.execute(" video_123 ", "approved")
|
|
|
|
mock_repo.update_review_status.assert_called_once_with("video_123", "approved")
|
|
|
|
|
|
class TestGetVideosByIdsUseCase:
|
|
"""GetVideosByIdsUseCase 测试"""
|
|
|
|
def test_get_by_ids(self, mock_repo, sample_video):
|
|
"""按ID批量获取"""
|
|
video2 = GeneratedVideo.create(
|
|
project_id="proj_1",
|
|
generation_task_id="task_2",
|
|
name="视频2",
|
|
file_url="https://oss.example.com/videos/v2.mp4",
|
|
)
|
|
video2.id = "video_456"
|
|
mock_repo.get_by_ids.return_value = [sample_video, video2]
|
|
use_case = GetVideosByIdsUseCase(mock_repo)
|
|
|
|
result = use_case.execute(["video_123", "video_456"])
|
|
|
|
assert len(result) == 2
|
|
mock_repo.get_by_ids.assert_called_once_with(["video_123", "video_456"])
|
|
|
|
def test_empty_list(self, mock_repo):
|
|
"""空ID列表返回空"""
|
|
mock_repo.get_by_ids.return_value = []
|
|
use_case = GetVideosByIdsUseCase(mock_repo)
|
|
|
|
result = use_case.execute([])
|
|
|
|
assert result == []
|