112021c16c
- 查重引擎单元测试 (test_dedup_engine.py): 25 用例 - hamming_distance XOR bit 计数验证 - compute_phash / compute_color_histogram(需 cv2,无则跳过) - check_duplicate 相似度判定逻辑(MD5 精确匹配、pHash 阈值、首次匹配返回) - 查重领域模型测试 (test_duplication_domain.py): 22 用例 - DuplicationRecord.create() 工厂方法校验 - 状态转换(pending → processing → completed/failed) - DuplicateSegment.create() 参数校验 - 查重用例层测试 (test_duplication_use_cases.py): 15 用例 - UploadForDuplicationUseCase / ListDuplicationRecordsUseCase - GetDuplicationDetailUseCase / DeleteDuplicationRecordUseCase - RetryDuplicationUseCase 状态重置逻辑 - 查重 API 集成测试 (test_duplication_api.py): 20 用例 - 列表/详情/删除/重试 4 个端点的正常流程与异常场景 - 跨用户隔离验证 - 跨端点组合场景测试 覆盖率: 422 passed, 8 skipped (cv2-dependent), 0 failures
276 lines
9.2 KiB
Python
276 lines
9.2 KiB
Python
"""查重域模型单元测试。
|
||
|
||
覆盖:
|
||
- DuplicationRecord.create() 工厂方法及验证
|
||
- DuplicationRecord 状态转换(mark_processing / mark_completed / mark_failed)
|
||
- DuplicationRecord.can_retry() / reset_for_retry()
|
||
- DuplicateSegment.create() 工厂方法及验证
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
|
||
from packages.domain.duplication import DuplicateSegment, DuplicationRecord
|
||
|
||
|
||
class TestDuplicationRecordCreate:
|
||
"""DuplicationRecord.create() 工厂方法测试。"""
|
||
|
||
def test_create_success(self):
|
||
record = DuplicationRecord.create(
|
||
user_id="user-1",
|
||
filename="test.mp4",
|
||
file_size=1024,
|
||
storage_key="oss/key/test.mp4",
|
||
duration_seconds=30.0,
|
||
)
|
||
assert record.user_id == "user-1"
|
||
assert record.filename == "test.mp4"
|
||
assert record.file_size == 1024
|
||
assert record.storage_key == "oss/key/test.mp4"
|
||
assert record.duration_seconds == 30.0
|
||
assert record.status == "pending"
|
||
assert record.duplicate_rate is None
|
||
assert record.duplicate_count == 0
|
||
assert record.error_message == ""
|
||
assert record.segments == []
|
||
assert record.video_fingerprint is None
|
||
assert record.id # 自动生成 ID
|
||
|
||
def test_create_with_default_duration(self):
|
||
record = DuplicationRecord.create(
|
||
user_id="user-1",
|
||
filename="test.mp4",
|
||
file_size=1024,
|
||
storage_key="oss/key",
|
||
)
|
||
assert record.duration_seconds == 0.0
|
||
|
||
def test_create_empty_user_id_raises(self):
|
||
with pytest.raises(ValueError, match="user_id"):
|
||
DuplicationRecord.create(
|
||
user_id="",
|
||
filename="test.mp4",
|
||
file_size=1024,
|
||
storage_key="oss/key",
|
||
)
|
||
|
||
def test_create_whitespace_user_id_raises(self):
|
||
with pytest.raises(ValueError, match="user_id"):
|
||
DuplicationRecord.create(
|
||
user_id=" ",
|
||
filename="test.mp4",
|
||
file_size=1024,
|
||
storage_key="oss/key",
|
||
)
|
||
|
||
def test_create_empty_filename_raises(self):
|
||
with pytest.raises(ValueError, match="filename"):
|
||
DuplicationRecord.create(
|
||
user_id="user-1",
|
||
filename="",
|
||
file_size=1024,
|
||
storage_key="oss/key",
|
||
)
|
||
|
||
def test_create_zero_file_size_raises(self):
|
||
with pytest.raises(ValueError, match="file_size"):
|
||
DuplicationRecord.create(
|
||
user_id="user-1",
|
||
filename="test.mp4",
|
||
file_size=0,
|
||
storage_key="oss/key",
|
||
)
|
||
|
||
def test_create_negative_file_size_raises(self):
|
||
with pytest.raises(ValueError, match="file_size"):
|
||
DuplicationRecord.create(
|
||
user_id="user-1",
|
||
filename="test.mp4",
|
||
file_size=-100,
|
||
storage_key="oss/key",
|
||
)
|
||
|
||
|
||
class TestDuplicationRecordStateTransitions:
|
||
"""状态转换测试。"""
|
||
|
||
@pytest.fixture
|
||
def record(self):
|
||
return DuplicationRecord.create(
|
||
user_id="user-1",
|
||
filename="test.mp4",
|
||
file_size=1024,
|
||
storage_key="oss/key",
|
||
)
|
||
|
||
def test_mark_processing(self, record):
|
||
record.mark_processing()
|
||
assert record.status == "processing"
|
||
|
||
def test_mark_completed_success(self, record):
|
||
record.mark_processing()
|
||
segments = [
|
||
DuplicateSegment.create(
|
||
source_start=0.0,
|
||
source_end=5.0,
|
||
matched_video_id="vid-1",
|
||
matched_video_name="existing.mp4",
|
||
matched_start=0.0,
|
||
matched_end=5.0,
|
||
similarity=92.5,
|
||
)
|
||
]
|
||
record.mark_completed(duplicate_rate=15.0, duplicate_count=1, segments=segments)
|
||
assert record.status == "completed"
|
||
assert record.duplicate_rate == 15.0
|
||
assert record.duplicate_count == 1
|
||
assert len(record.segments) == 1
|
||
|
||
def test_mark_completed_invalid_rate_raises(self, record):
|
||
record.mark_processing()
|
||
with pytest.raises(ValueError, match="duplicate_rate"):
|
||
record.mark_completed(duplicate_rate=101.0, duplicate_count=0, segments=[])
|
||
|
||
def test_mark_completed_negative_rate_raises(self, record):
|
||
record.mark_processing()
|
||
with pytest.raises(ValueError, match="duplicate_rate"):
|
||
record.mark_completed(duplicate_rate=-1.0, duplicate_count=0, segments=[])
|
||
|
||
def test_mark_failed(self, record):
|
||
record.mark_processing()
|
||
record.mark_failed("处理超时")
|
||
assert record.status == "failed"
|
||
assert record.error_message == "处理超时"
|
||
|
||
|
||
class TestDuplicationRecordRetry:
|
||
"""can_retry() 和 reset_for_retry() 测试。"""
|
||
|
||
@pytest.fixture
|
||
def record(self):
|
||
return DuplicationRecord.create(
|
||
user_id="user-1",
|
||
filename="test.mp4",
|
||
file_size=1024,
|
||
storage_key="oss/key",
|
||
)
|
||
|
||
def test_mark_failed_sets_status_and_error(self, record):
|
||
record.mark_processing()
|
||
record.mark_failed("处理失败")
|
||
assert record.status == "failed"
|
||
assert record.error_message == "处理失败"
|
||
|
||
def test_mark_failed_updates_timestamp(self, record):
|
||
old_updated = record.updated_at
|
||
record.mark_processing()
|
||
record.mark_failed("错误")
|
||
assert record.updated_at >= old_updated
|
||
|
||
def test_failed_record_preserves_result_fields(self, record):
|
||
"""mark_failed 不改变 duplicate_rate 等结果字段(由 use case 层重置)。"""
|
||
record.mark_processing()
|
||
record.mark_completed(duplicate_rate=10.0, duplicate_count=1, segments=[])
|
||
record.mark_failed("重试失败")
|
||
assert record.status == "failed"
|
||
assert record.error_message == "重试失败"
|
||
assert record.duplicate_rate == 10.0
|
||
|
||
|
||
class TestDuplicateSegmentCreate:
|
||
"""DuplicateSegment.create() 工厂方法测试。"""
|
||
|
||
def test_create_success(self):
|
||
seg = DuplicateSegment.create(
|
||
source_start=1.0,
|
||
source_end=5.0,
|
||
matched_video_id="vid-1",
|
||
matched_video_name="existing.mp4",
|
||
matched_start=2.0,
|
||
matched_end=6.0,
|
||
similarity=85.5,
|
||
)
|
||
assert seg.source_start == 1.0
|
||
assert seg.source_end == 5.0
|
||
assert seg.matched_video_id == "vid-1"
|
||
assert seg.matched_video_name == "existing.mp4"
|
||
assert seg.matched_start == 2.0
|
||
assert seg.matched_end == 6.0
|
||
assert seg.similarity == 85.5
|
||
assert seg.id # 自动生成 ID
|
||
|
||
def test_create_negative_source_start_raises(self):
|
||
with pytest.raises(ValueError, match="invalid source segment range"):
|
||
DuplicateSegment.create(
|
||
source_start=-1.0,
|
||
source_end=5.0,
|
||
matched_video_id="vid-1",
|
||
matched_video_name="v.mp4",
|
||
matched_start=0.0,
|
||
matched_end=5.0,
|
||
similarity=80.0,
|
||
)
|
||
|
||
def test_create_source_end_le_start_raises(self):
|
||
with pytest.raises(ValueError, match="invalid source segment range"):
|
||
DuplicateSegment.create(
|
||
source_start=5.0,
|
||
source_end=5.0,
|
||
matched_video_id="vid-1",
|
||
matched_video_name="v.mp4",
|
||
matched_start=0.0,
|
||
matched_end=5.0,
|
||
similarity=80.0,
|
||
)
|
||
|
||
def test_create_negative_matched_start_raises(self):
|
||
with pytest.raises(ValueError, match="invalid matched segment range"):
|
||
DuplicateSegment.create(
|
||
source_start=0.0,
|
||
source_end=5.0,
|
||
matched_video_id="vid-1",
|
||
matched_video_name="v.mp4",
|
||
matched_start=-1.0,
|
||
matched_end=5.0,
|
||
similarity=80.0,
|
||
)
|
||
|
||
def test_create_matched_end_le_start_raises(self):
|
||
with pytest.raises(ValueError, match="invalid matched segment range"):
|
||
DuplicateSegment.create(
|
||
source_start=0.0,
|
||
source_end=5.0,
|
||
matched_video_id="vid-1",
|
||
matched_video_name="v.mp4",
|
||
matched_start=2.0,
|
||
matched_end=1.0,
|
||
similarity=80.0,
|
||
)
|
||
|
||
def test_create_similarity_out_of_range_raises(self):
|
||
with pytest.raises(ValueError, match="similarity"):
|
||
DuplicateSegment.create(
|
||
source_start=0.0,
|
||
source_end=5.0,
|
||
matched_video_id="vid-1",
|
||
matched_video_name="v.mp4",
|
||
matched_start=0.0,
|
||
matched_end=5.0,
|
||
similarity=101.0,
|
||
)
|
||
|
||
def test_create_negative_similarity_raises(self):
|
||
with pytest.raises(ValueError, match="similarity"):
|
||
DuplicateSegment.create(
|
||
source_start=0.0,
|
||
source_end=5.0,
|
||
matched_video_id="vid-1",
|
||
matched_video_name="v.mp4",
|
||
matched_start=0.0,
|
||
matched_end=5.0,
|
||
similarity=-1.0,
|
||
)
|
||
|