Files
xiaoxia-saas/tests/unit/test_generated_videos_use_cases.py
T
CI Bot bb95bb559a
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 59s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m2s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m20s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m19s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 55s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 4m24s
AI Code Review / AI Code Review (pull_request) Successful in 4m11s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 19s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m47s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 6m28s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 11m51s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m6s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 4m46s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 49m10s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 7s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1299h42m47s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1299h57m3s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1299h57m7s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1299h57m9s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1300h21m44s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1300h21m53s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1300h21m55s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 1300h21m57s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1300h37m33s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1300h37m37s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1300h29m31s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1300h54m17s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1301h10m1s
test: P3-1 第33波单元测试
- projects use case: 30个测试(列表/获取/创建/共享/取消共享/删除/命令对象)
- generated_videos use case: 27个测试(列表/分页/获取/按任务查询/下载URL/审核状态/批量获取)
- duplication use case: 12个测试(上传/列表/详情/删除/重试/命令对象)

合计+69
2026-07-24 10:45:01 +08:00

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 == []