Files
xiaoxia-saas/tests/unit/test_generation_tasks.py
T
xiaoxia-agent 421cc3f25d
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 7s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m56s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m43s
AI Code Review / AI Code Review (pull_request) Failing after 21m29s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 23s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 6m34s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 4m4s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 6m35s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 7m12s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 8m23s
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 14h23m52s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 14h12m39s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 14h12m39s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 14h12m40s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 14h23m9s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 14h23m9s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 14h23m21s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 14h48m25s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 14h23m32s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 14h23m32s
feat(points): P2 step 2.4 - wire generation_tasks points gate (ai_video scene)
2026-09-16 00:23:31 +08:00

261 lines
8.8 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""生成任务 UseCase 单元测试."""
from __future__ import annotations
from unittest.mock import MagicMock
import pytest
from packages.application.generation_tasks import (
CreateGenerationTaskCommand,
CreateGenerationTaskUseCase,
GetGenerationTaskUseCase,
ListGenerationTasksResult,
ListTasksFilter,
ListUserTasksFilteredUseCase,
RetryGenerationTaskUseCase,
)
from packages.domain import GenerationTask
import packages.middleware.points_gate as _pg_module
@pytest.fixture(autouse=True)
def _disable_points_gate(monkeypatch):
"""默认关闭积分闸门,避免影响既有用例。"""
monkeypatch.setattr(_pg_module, "_points_gate_enabled", lambda: False)
yield
@pytest.fixture
def mock_repo():
return MagicMock()
@pytest.fixture
def sample_task():
task = MagicMock(spec=GenerationTask)
task.id = "task_001"
task.project_id = "proj_001"
task.status = "pending"
return task
class TestCreateGenerationTaskUseCase:
"""CreateGenerationTaskUseCase 测试"""
def test_create_task_success(self, mock_repo):
"""正常创建生成任务"""
mock_repo.create.side_effect = lambda t: t
use_case = CreateGenerationTaskUseCase(mock_repo)
command = CreateGenerationTaskCommand(
project_id="proj_001",
template_id="tpl_001",
asset_library_id="lib_001",
voice_library_id="voice_lib_001",
created_by_user_id="user_001",
)
result = use_case.execute(command)
assert isinstance(result, GenerationTask)
assert result.project_id == "proj_001"
assert result.template_id == "tpl_001"
assert result.status == "pending"
assert result.progress == 0.0
assert result.result_count == 0
mock_repo.create.assert_called_once()
def test_create_task_generates_id(self, mock_repo):
"""创建任务时生成 id"""
mock_repo.create.side_effect = lambda t: t
use_case = CreateGenerationTaskUseCase(mock_repo)
command = CreateGenerationTaskCommand(project_id="proj_001")
result = use_case.execute(command)
assert result.id is not None
assert len(result.id) > 0
def test_create_task_with_asset_ids(self, mock_repo):
"""创建带 asset_ids 的任务"""
mock_repo.create.side_effect = lambda t: t
use_case = CreateGenerationTaskUseCase(mock_repo)
command = CreateGenerationTaskCommand(
project_id="proj_001",
asset_ids=["asset_1", "asset_2", "asset_3"],
title_ids=["title_1", "title_2"],
)
result = use_case.execute(command)
assert len(result.asset_ids) == 3
assert len(result.title_ids) == 2
# #1749:voice_ids 已从 Command 移除(配音走 voice_library_id(s)),落库固定空列表
assert list(getattr(result, "voice_ids", []) or []) == []
def test_create_task_with_auto_retry(self, mock_repo):
"""创建带自动重试配置的任务"""
mock_repo.create.side_effect = lambda t: t
use_case = CreateGenerationTaskUseCase(mock_repo)
command = CreateGenerationTaskCommand(
project_id="proj_001",
auto_retry_enabled=True,
auto_retry_max=3,
)
result = use_case.execute(command)
assert result.auto_retry_enabled is True
assert result.auto_retry_max == 3
def test_create_task_with_bgm_config(self, mock_repo):
"""创建带 BGM 配置的任务"""
mock_repo.create.side_effect = lambda t: t
use_case = CreateGenerationTaskUseCase(mock_repo)
bgm = {"enabled": True, "volume": 0.5, "library_id": "bgm_lib"}
command = CreateGenerationTaskCommand(
project_id="proj_001",
bgm_config=bgm,
resolution="1080p",
video_title="测试视频",
)
result = use_case.execute(command)
assert result.bgm_config == bgm
assert result.resolution == "1080p"
assert result.video_title == "测试视频"
def test_create_task_defaults(self, mock_repo):
"""默认参数的任务"""
mock_repo.create.side_effect = lambda t: t
use_case = CreateGenerationTaskUseCase(mock_repo)
command = CreateGenerationTaskCommand()
result = use_case.execute(command)
assert result.project_id == ""
assert result.asset_ids == []
assert result.auto_retry_enabled is False
assert result.auto_retry_max == 0
class TestGetGenerationTaskUseCase:
"""GetGenerationTaskUseCase 测试"""
def test_get_task_success(self, mock_repo, sample_task):
"""获取任务成功"""
mock_repo.get.return_value = sample_task
use_case = GetGenerationTaskUseCase(mock_repo)
result = use_case.execute("task_001")
assert result is sample_task
mock_repo.get.assert_called_once_with("task_001")
def test_get_task_not_found(self, mock_repo):
"""任务不存在返回 None"""
mock_repo.get.return_value = None
use_case = GetGenerationTaskUseCase(mock_repo)
result = use_case.execute("nonexistent")
assert result is None
class TestListUserTasksFilteredUseCase:
"""ListUserTasksFilteredUseCase 测试"""
def test_list_without_filter(self, mock_repo, sample_task):
"""不带筛选条件查询"""
mock_repo.list_by_user_filtered.return_value = [sample_task]
mock_repo.count_by_user_filtered.return_value = 1
use_case = ListUserTasksFilteredUseCase(mock_repo)
result = use_case.execute("user_001")
assert isinstance(result, ListGenerationTasksResult)
assert len(result.items) == 1
assert result.total == 1
mock_repo.list_by_user_filtered.assert_called_once_with("user_001", status=None, limit=None, offset=0)
def test_list_with_status_filter(self, mock_repo):
"""按状态筛选"""
mock_repo.list_by_user_filtered.return_value = []
mock_repo.count_by_user_filtered.return_value = 0
use_case = ListUserTasksFilteredUseCase(mock_repo)
result = use_case.execute("user_001", status="completed")
assert result.total == 0
mock_repo.list_by_user_filtered.assert_called_once_with("user_001", status="completed", limit=None, offset=0)
def test_list_with_pagination(self, mock_repo):
"""带分页参数查询"""
mock_repo.list_by_user_filtered.return_value = []
mock_repo.count_by_user_filtered.return_value = 50
use_case = ListUserTasksFilteredUseCase(mock_repo)
result = use_case.execute("user_001", limit=10, offset=20)
assert result.total == 50
mock_repo.list_by_user_filtered.assert_called_once_with("user_001", status=None, limit=10, offset=20)
def test_list_with_all_params(self, mock_repo):
"""带所有筛选和分页参数"""
mock_repo.list_by_user_filtered.return_value = []
mock_repo.count_by_user_filtered.return_value = 5
use_case = ListUserTasksFilteredUseCase(mock_repo)
use_case.execute("user_001", status="failed", limit=20, offset=0)
mock_repo.list_by_user_filtered.assert_called_once_with("user_001", status="failed", limit=20, offset=0)
mock_repo.count_by_user_filtered.assert_called_once_with("user_001", status="failed")
class TestRetryGenerationTaskUseCase:
"""RetryGenerationTaskUseCase 测试"""
def test_retry_failed_task(self, mock_repo):
"""重试失败的任务"""
task = MagicMock(spec=GenerationTask)
task.is_failed = True
mock_repo.get.return_value = task
mock_repo.update.return_value = task
use_case = RetryGenerationTaskUseCase(mock_repo)
result = use_case.execute("task_001")
task.mark_pending_from_failed.assert_called_once()
mock_repo.update.assert_called_once_with(task)
assert result is task
def test_retry_not_found(self, mock_repo):
"""任务不存在抛出 ValueError"""
mock_repo.get.return_value = None
use_case = RetryGenerationTaskUseCase(mock_repo)
with pytest.raises(ValueError, match="任务不存在"):
use_case.execute("nonexistent")
mock_repo.update.assert_not_called()
def test_retry_non_failed_task(self, mock_repo):
"""非失败状态的任务不能重试"""
task = MagicMock(spec=GenerationTask)
task.is_failed = False
task.status = MagicMock()
task.status.value = "running"
mock_repo.get.return_value = task
use_case = RetryGenerationTaskUseCase(mock_repo)
with pytest.raises(ValueError, match="只有失败状态的任务才能重试"):
use_case.execute("task_001")
mock_repo.update.assert_not_called()
task.mark_pending_from_failed.assert_not_called()