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
300 lines
9.4 KiB
Python
Executable File
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)
|