Files
xiaoxia-saas/tests/unit/test_duplication_use_cases.py
T
xiaoxia 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
test: P3-1 第33波单元测试(projects/generated_videos/duplication) (#810)
2026-07-24 11:25:49 +08:00

300 lines
9.4 KiB
Python
Executable File

"""查重 UseCase 单元测试."""
from __future__ import annotations
from unittest.mock import MagicMock
import pytest
from packages.application.duplication import (
DeleteDuplicationRecordUseCase,
GetDuplicationDetailUseCase,
ListDuplicationRecordsUseCase,
RetryDuplicationUseCase,
UploadForDuplicationCommand,
UploadForDuplicationUseCase,
)
from packages.domain.duplication import DuplicationRecord
@pytest.fixture
def mock_repo():
return MagicMock()
@pytest.fixture
def sample_record():
r = DuplicationRecord.create(
user_id="user_1",
filename="test_video.mp4",
file_size=1048576,
storage_key="uploads/test_video.mp4",
duration_seconds=30.5,
)
r.id = "dup_123"
return r
@pytest.fixture
def failed_record():
r = DuplicationRecord.create(
user_id="user_1",
filename="failed.mp4",
file_size=512000,
storage_key="uploads/failed.mp4",
)
r.id = "dup_456"
r.mark_failed("网络超时")
return r
class TestUploadForDuplicationUseCase:
"""UploadForDuplicationUseCase 测试"""
def test_upload_success(self, mock_repo, sample_record):
"""上传查重成功"""
mock_repo.create.return_value = sample_record
use_case = UploadForDuplicationUseCase(mock_repo)
command = UploadForDuplicationCommand(
user_id="user_1",
filename="test_video.mp4",
file_size=1048576,
storage_key="uploads/test_video.mp4",
duration_seconds=30.5,
)
result = use_case.execute(command)
assert result.id == "dup_123"
assert result.user_id == "user_1"
mock_repo.create.assert_called_once()
created = mock_repo.create.call_args[0][0]
assert isinstance(created, DuplicationRecord)
assert created.status == "pending"
def test_upload_default_duration(self, mock_repo):
"""不传 duration 默认 0.0"""
mock_repo.create.side_effect = lambda x: x
use_case = UploadForDuplicationUseCase(mock_repo)
command = UploadForDuplicationCommand(
user_id="user_1",
filename="test.mp4",
file_size=1000,
storage_key="key",
)
result = use_case.execute(command)
assert result.duration_seconds == 0.0
def test_upload_empty_user_id_raises(self, mock_repo):
"""空 user_id 在 domain 层抛出"""
use_case = UploadForDuplicationUseCase(mock_repo)
command = UploadForDuplicationCommand(
user_id="",
filename="test.mp4",
file_size=1000,
storage_key="key",
)
with pytest.raises(ValueError, match="user_id cannot be empty"):
use_case.execute(command)
mock_repo.create.assert_not_called()
def test_upload_zero_file_size_raises(self, mock_repo):
"""文件大小为0抛出"""
use_case = UploadForDuplicationUseCase(mock_repo)
command = UploadForDuplicationCommand(
user_id="user_1",
filename="test.mp4",
file_size=0,
storage_key="key",
)
with pytest.raises(ValueError, match="file_size must be positive"):
use_case.execute(command)
class TestListDuplicationRecordsUseCase:
"""ListDuplicationRecordsUseCase 测试"""
def test_list_returns_results(self, mock_repo, sample_record):
"""正常返回用户查重记录列表"""
mock_repo.list_by_user.return_value = [sample_record]
use_case = ListDuplicationRecordsUseCase(mock_repo)
result = use_case.execute("user_1")
assert len(result) == 1
assert result[0].id == "dup_123"
mock_repo.list_by_user.assert_called_once_with("user_1", offset=0, limit=50)
def test_list_with_offset_limit(self, mock_repo, sample_record):
"""带 offset 和 limit 参数"""
mock_repo.list_by_user.return_value = [sample_record]
use_case = ListDuplicationRecordsUseCase(mock_repo)
use_case.execute("user_1", offset=10, limit=20)
mock_repo.list_by_user.assert_called_once_with("user_1", offset=10, limit=20)
def test_empty_user_id_raises(self, mock_repo):
"""空 user_id 抛出"""
use_case = ListDuplicationRecordsUseCase(mock_repo)
with pytest.raises(ValueError, match="user_id 不能为空"):
use_case.execute("")
mock_repo.list_by_user.assert_not_called()
def test_whitespace_user_id_raises(self, mock_repo):
"""纯空格 user_id 抛出"""
use_case = ListDuplicationRecordsUseCase(mock_repo)
with pytest.raises(ValueError, match="user_id 不能为空"):
use_case.execute(" ")
def test_user_id_stripped(self, mock_repo, sample_record):
"""user_id 被 strip"""
mock_repo.list_by_user.return_value = [sample_record]
use_case = ListDuplicationRecordsUseCase(mock_repo)
use_case.execute(" user_1 ")
mock_repo.list_by_user.assert_called_once_with("user_1", offset=0, limit=50)
class TestGetDuplicationDetailUseCase:
"""GetDuplicationDetailUseCase 测试"""
def test_get_existing(self, mock_repo, sample_record):
"""获取存在的记录"""
mock_repo.get.return_value = sample_record
use_case = GetDuplicationDetailUseCase(mock_repo)
result = use_case.execute("dup_123")
assert result is not None
assert result.id == "dup_123"
mock_repo.get.assert_called_once_with("dup_123")
def test_get_nonexistent_returns_none(self, mock_repo):
"""获取不存在的记录返回 None"""
mock_repo.get.return_value = None
use_case = GetDuplicationDetailUseCase(mock_repo)
result = use_case.execute("nonexistent")
assert result is None
class TestDeleteDuplicationRecordUseCase:
"""DeleteDuplicationRecordUseCase 测试"""
def test_delete_success(self, mock_repo):
"""删除成功"""
mock_repo.delete.return_value = True
use_case = DeleteDuplicationRecordUseCase(mock_repo)
result = use_case.execute("dup_123")
assert result is True
mock_repo.delete.assert_called_once_with("dup_123")
def test_delete_nonexistent_returns_false(self, mock_repo):
"""删除不存在的记录返回 False"""
mock_repo.delete.return_value = False
use_case = DeleteDuplicationRecordUseCase(mock_repo)
result = use_case.execute("nonexistent")
assert result is False
class TestRetryDuplicationUseCase:
"""RetryDuplicationUseCase 测试"""
def test_retry_failed_record(self, mock_repo, failed_record):
"""失败记录可以重试,状态重置为 pending"""
mock_repo.get.return_value = failed_record
mock_repo.update.side_effect = lambda x: x
use_case = RetryDuplicationUseCase(mock_repo)
result = use_case.execute("dup_456")
assert result is not None
assert result.status == "pending"
assert result.error_message == ""
assert result.duplicate_rate is None
assert result.duplicate_count == 0
assert result.segments == []
mock_repo.update.assert_called_once()
def test_retry_nonexistent_returns_none(self, mock_repo):
"""重试不存在的记录返回 None"""
mock_repo.get.return_value = None
use_case = RetryDuplicationUseCase(mock_repo)
result = use_case.execute("nonexistent")
assert result is None
mock_repo.update.assert_not_called()
def test_retry_pending_raises(self, mock_repo, sample_record):
"""pending 状态的记录不能重试"""
assert sample_record.status == "pending"
mock_repo.get.return_value = sample_record
use_case = RetryDuplicationUseCase(mock_repo)
with pytest.raises(ValueError, match="只有 failed 状态的记录可以重试"):
use_case.execute("dup_123")
mock_repo.update.assert_not_called()
def test_retry_completed_raises(self, mock_repo, sample_record):
"""completed 状态的记录不能重试"""
sample_record.mark_completed(duplicate_rate=25.5, duplicate_count=3, segments=[])
mock_repo.get.return_value = sample_record
use_case = RetryDuplicationUseCase(mock_repo)
with pytest.raises(ValueError, match="只有 failed 状态的记录可以重试"):
use_case.execute("dup_123")
mock_repo.update.assert_not_called()
class TestUploadForDuplicationCommand:
"""UploadForDuplicationCommand 数据类测试"""
def test_command_fields(self):
"""命令对象字段正确"""
cmd = UploadForDuplicationCommand(
user_id="user_1",
filename="test.mp4",
file_size=1024,
storage_key="uploads/test.mp4",
duration_seconds=15.0,
)
assert cmd.user_id == "user_1"
assert cmd.filename == "test.mp4"
assert cmd.file_size == 1024
assert cmd.storage_key == "uploads/test.mp4"
assert cmd.duration_seconds == 15.0
def test_default_duration(self):
"""duration_seconds 默认 0.0"""
cmd = UploadForDuplicationCommand(
user_id="user_1",
filename="test.mp4",
file_size=1024,
storage_key="key",
)
assert cmd.duration_seconds == 0.0
def test_command_is_dataclass(self):
"""是 dataclass"""
from dataclasses import is_dataclass
assert is_dataclass(UploadForDuplicationCommand)