3921a657e8
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 52s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m56s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m7s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m7s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 54s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 6m27s
CI/CD Pipeline / Unit Tests (push) Failing after 8m24s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 16m1s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / Integration Tests (push) Successful in 3m28s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 21s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m26s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m34s
317 lines
11 KiB
Python
Executable File
317 lines
11 KiB
Python
Executable File
"""Job Use Cases 单元测试"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from packages.application.jobs import (
|
|
CancelJobUseCase,
|
|
CompleteJobCommand,
|
|
CompleteJobUseCase,
|
|
CreateJobCommand,
|
|
CreateJobUseCase,
|
|
FailJobCommand,
|
|
FailJobUseCase,
|
|
GetJobStatisticsUseCase,
|
|
GetJobUseCase,
|
|
ListJobsUseCase,
|
|
RetryJobUseCase,
|
|
SubmitJobUseCase,
|
|
UpdateJobProgressCommand,
|
|
UpdateJobProgressUseCase,
|
|
)
|
|
from packages.domain.job import Job, JobStatus, JobType
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_repo():
|
|
return MagicMock()
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_job():
|
|
return Job.create(
|
|
project_id="proj_001",
|
|
job_type=JobType.VIDEO_COMPOSE,
|
|
payload={"template_id": "tpl_001"},
|
|
source_id="src_001",
|
|
created_by_user_id="user_001",
|
|
max_retries=3,
|
|
)
|
|
|
|
|
|
class TestCreateJobCommand:
|
|
"""CreateJobCommand 测试"""
|
|
|
|
def test_default_values(self):
|
|
cmd = CreateJobCommand(project_id="p1", job_type=JobType.VIDEO_COMPOSE)
|
|
assert cmd.project_id == "p1"
|
|
assert cmd.payload == {}
|
|
assert cmd.source_id == ""
|
|
assert cmd.created_by_user_id == ""
|
|
assert cmd.max_retries == 3
|
|
|
|
|
|
class TestCreateJobUseCase:
|
|
"""CreateJobUseCase 测试"""
|
|
|
|
def test_create_success(self, mock_repo, sample_job):
|
|
mock_repo.create.return_value = sample_job
|
|
use_case = CreateJobUseCase(mock_repo)
|
|
cmd = CreateJobCommand(
|
|
project_id="proj_001",
|
|
job_type=JobType.VIDEO_COMPOSE,
|
|
payload={"template_id": "tpl_001"},
|
|
source_id="src_001",
|
|
created_by_user_id="user_001",
|
|
max_retries=3,
|
|
)
|
|
result = use_case.execute(cmd)
|
|
assert result.status == JobStatus.PENDING
|
|
assert result.project_id == "proj_001"
|
|
mock_repo.create.assert_called_once()
|
|
|
|
def test_create_with_string_job_type(self, mock_repo):
|
|
mock_repo.create.side_effect = lambda x: x
|
|
use_case = CreateJobUseCase(mock_repo)
|
|
cmd = CreateJobCommand(project_id="p1", job_type="video_compose")
|
|
result = use_case.execute(cmd)
|
|
assert result.job_type == JobType.VIDEO_COMPOSE
|
|
|
|
|
|
class TestSubmitJobUseCase:
|
|
"""SubmitJobUseCase 测试"""
|
|
|
|
def test_submit_success(self, mock_repo, sample_job):
|
|
mock_repo.get.return_value = sample_job
|
|
mock_repo.update.side_effect = lambda x: x
|
|
use_case = SubmitJobUseCase(mock_repo)
|
|
result = use_case.execute(sample_job.id, celery_task_id="celery_123")
|
|
assert result.status == JobStatus.RUNNING
|
|
assert result.celery_task_id == "celery_123"
|
|
|
|
def test_submit_not_found(self, mock_repo):
|
|
mock_repo.get.return_value = None
|
|
use_case = SubmitJobUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="任务不存在"):
|
|
use_case.execute("nonexistent")
|
|
|
|
def test_submit_wrong_status(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.RUNNING
|
|
mock_repo.get.return_value = sample_job
|
|
use_case = SubmitJobUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="只有 pending"):
|
|
use_case.execute(sample_job.id)
|
|
|
|
|
|
class TestUpdateJobProgressUseCase:
|
|
"""UpdateJobProgressUseCase 测试"""
|
|
|
|
def test_update_progress_success(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.RUNNING
|
|
mock_repo.get.return_value = sample_job
|
|
mock_repo.update.side_effect = lambda x: x
|
|
use_case = UpdateJobProgressUseCase(mock_repo)
|
|
cmd = UpdateJobProgressCommand(job_id=sample_job.id, progress=50.0, current_stage="渲染中")
|
|
result = use_case.execute(cmd)
|
|
assert result.progress == 50.0
|
|
assert result.current_stage == "渲染中"
|
|
|
|
def test_update_progress_not_found(self, mock_repo):
|
|
mock_repo.get.return_value = None
|
|
use_case = UpdateJobProgressUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="任务不存在"):
|
|
use_case.execute(UpdateJobProgressCommand(job_id="x", progress=10))
|
|
|
|
def test_update_progress_wrong_status(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.PENDING
|
|
mock_repo.get.return_value = sample_job
|
|
use_case = UpdateJobProgressUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="只有 running"):
|
|
use_case.execute(UpdateJobProgressCommand(job_id=sample_job.id, progress=10))
|
|
|
|
|
|
class TestCompleteJobUseCase:
|
|
"""CompleteJobUseCase 测试"""
|
|
|
|
def test_complete_from_running(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.RUNNING
|
|
mock_repo.get.return_value = sample_job
|
|
mock_repo.update.side_effect = lambda x: x
|
|
use_case = CompleteJobUseCase(mock_repo)
|
|
cmd = CompleteJobCommand(job_id=sample_job.id, result={"url": "http://..."})
|
|
result = use_case.execute(cmd)
|
|
assert result.status == JobStatus.SUCCESS
|
|
assert result.result["url"] == "http://..."
|
|
|
|
def test_complete_from_pending(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.PENDING
|
|
mock_repo.get.return_value = sample_job
|
|
mock_repo.update.side_effect = lambda x: x
|
|
use_case = CompleteJobUseCase(mock_repo)
|
|
result = use_case.execute(CompleteJobCommand(job_id=sample_job.id))
|
|
assert result.status == JobStatus.SUCCESS
|
|
|
|
def test_complete_not_found(self, mock_repo):
|
|
mock_repo.get.return_value = None
|
|
use_case = CompleteJobUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="任务不存在"):
|
|
use_case.execute(CompleteJobCommand(job_id="x"))
|
|
|
|
def test_complete_failed_status_raises(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.FAILED
|
|
mock_repo.get.return_value = sample_job
|
|
use_case = CompleteJobUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="只有 running/pending"):
|
|
use_case.execute(CompleteJobCommand(job_id=sample_job.id))
|
|
|
|
|
|
class TestFailJobUseCase:
|
|
"""FailJobUseCase 测试"""
|
|
|
|
def test_fail_success(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.RUNNING
|
|
mock_repo.get.return_value = sample_job
|
|
mock_repo.update.side_effect = lambda x: x
|
|
use_case = FailJobUseCase(mock_repo)
|
|
cmd = FailJobCommand(job_id=sample_job.id, error_message="渲染失败")
|
|
result = use_case.execute(cmd)
|
|
assert result.status == JobStatus.FAILED
|
|
assert "渲染失败" in result.error_message
|
|
|
|
def test_fail_not_found(self, mock_repo):
|
|
mock_repo.get.return_value = None
|
|
use_case = FailJobUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="任务不存在"):
|
|
use_case.execute(FailJobCommand(job_id="x", error_message="err"))
|
|
|
|
def test_fail_updates_error_message(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.RUNNING
|
|
mock_repo.get.return_value = sample_job
|
|
mock_repo.update.side_effect = lambda x: x
|
|
use_case = FailJobUseCase(mock_repo)
|
|
result = use_case.execute(FailJobCommand(job_id=sample_job.id, error_message="连接超时"))
|
|
assert result.error_message == "连接超时"
|
|
|
|
|
|
class TestRetryJobUseCase:
|
|
"""RetryJobUseCase 测试"""
|
|
|
|
def test_retry_success(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.FAILED
|
|
sample_job.retry_count = 1
|
|
mock_repo.get.return_value = sample_job
|
|
mock_repo.update.side_effect = lambda x: x
|
|
use_case = RetryJobUseCase(mock_repo)
|
|
result = use_case.execute(sample_job.id)
|
|
assert result.status == JobStatus.PENDING
|
|
assert result.retry_count == 2
|
|
|
|
def test_retry_not_found(self, mock_repo):
|
|
mock_repo.get.return_value = None
|
|
use_case = RetryJobUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="任务不存在"):
|
|
use_case.execute("nonexistent")
|
|
|
|
|
|
class TestCancelJobUseCase:
|
|
"""CancelJobUseCase 测试"""
|
|
|
|
def test_cancel_pending(self, mock_repo, sample_job):
|
|
mock_repo.get.return_value = sample_job
|
|
mock_repo.update.side_effect = lambda x: x
|
|
use_case = CancelJobUseCase(mock_repo)
|
|
result = use_case.execute(sample_job.id)
|
|
assert result.status == JobStatus.CANCELLED
|
|
|
|
def test_cancel_running(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.RUNNING
|
|
mock_repo.get.return_value = sample_job
|
|
mock_repo.update.side_effect = lambda x: x
|
|
use_case = CancelJobUseCase(mock_repo)
|
|
result = use_case.execute(sample_job.id)
|
|
assert result.status == JobStatus.CANCELLED
|
|
|
|
def test_cancel_terminal_raises(self, mock_repo, sample_job):
|
|
sample_job.status = JobStatus.SUCCESS
|
|
mock_repo.get.return_value = sample_job
|
|
use_case = CancelJobUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="终态"):
|
|
use_case.execute(sample_job.id)
|
|
|
|
def test_cancel_not_found(self, mock_repo):
|
|
mock_repo.get.return_value = None
|
|
use_case = CancelJobUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="任务不存在"):
|
|
use_case.execute("nonexistent")
|
|
|
|
|
|
class TestGetJobUseCase:
|
|
"""GetJobUseCase 测试"""
|
|
|
|
def test_get_exists(self, mock_repo, sample_job):
|
|
mock_repo.get.return_value = sample_job
|
|
use_case = GetJobUseCase(mock_repo)
|
|
result = use_case.execute(sample_job.id)
|
|
assert result.id == sample_job.id
|
|
|
|
def test_get_not_found(self, mock_repo):
|
|
mock_repo.get.return_value = None
|
|
use_case = GetJobUseCase(mock_repo)
|
|
result = use_case.execute("nonexistent")
|
|
assert result is None
|
|
|
|
|
|
class TestListJobsUseCase:
|
|
"""ListJobsUseCase 测试"""
|
|
|
|
def test_list_by_project(self, mock_repo, sample_job):
|
|
mock_repo.list_by_project.return_value = [sample_job]
|
|
use_case = ListJobsUseCase(mock_repo)
|
|
result = use_case.execute(project_id="proj_001")
|
|
assert len(result) == 1
|
|
mock_repo.list_by_project.assert_called_once()
|
|
|
|
def test_list_by_user(self, mock_repo, sample_job):
|
|
mock_repo.list_by_user.return_value = [sample_job]
|
|
use_case = ListJobsUseCase(mock_repo)
|
|
result = use_case.execute(user_id="user_001")
|
|
assert len(result) == 1
|
|
mock_repo.list_by_user.assert_called_once()
|
|
|
|
def test_list_no_filter_raises(self, mock_repo):
|
|
use_case = ListJobsUseCase(mock_repo)
|
|
with pytest.raises(ValueError, match="必须指定"):
|
|
use_case.execute()
|
|
|
|
def test_list_with_filters(self, mock_repo, sample_job):
|
|
mock_repo.list_by_project.return_value = [sample_job]
|
|
use_case = ListJobsUseCase(mock_repo)
|
|
use_case.execute(
|
|
project_id="p1",
|
|
job_type=JobType.VIDEO_COMPOSE,
|
|
status=JobStatus.RUNNING,
|
|
limit=20,
|
|
offset=10,
|
|
)
|
|
mock_repo.list_by_project.assert_called_once_with(
|
|
"p1", job_type=JobType.VIDEO_COMPOSE, status=JobStatus.RUNNING, limit=20, offset=10
|
|
)
|
|
|
|
|
|
class TestGetJobStatisticsUseCase:
|
|
"""GetJobStatisticsUseCase 测试"""
|
|
|
|
def test_statistics(self, mock_repo):
|
|
mock_repo.count_by_project.side_effect = [10, 2, 3, 4, 1]
|
|
use_case = GetJobStatisticsUseCase(mock_repo)
|
|
stats = use_case.execute("proj_001")
|
|
assert stats["project_id"] == "proj_001"
|
|
assert stats["total"] == 10
|
|
assert stats["pending"] == 2
|
|
assert stats["running"] == 3
|
|
assert stats["success"] == 4
|
|
assert stats["failed"] == 1
|