Files
xiaoxia-saas/tests/unit/test_duplication_domain.py
xiaoxia df38101bd9
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 40s
CI/CD Pipeline / Frontend Lint (push) Successful in 2m26s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 3m35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m55s
CI/CD Pipeline / Unit Tests (push) Successful in 4m6s
CI/CD Pipeline / Integration Tests (push) Successful in 1m45s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m21s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 15m52s
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 / ACR Image Cleanup (push) Has been cancelled
test(unit): P3-1 核心模块单元测试覆盖率提升 - 新增12个模块400+测试 (#661)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-21 00:21:37 +08:00

352 lines
13 KiB
Python
Executable File
Raw Permalink 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.
"""
Duplication 查重记录领域模型单元测试
"""
import pytest
from packages.domain.duplication import DuplicateSegment, DuplicationRecord
class TestDuplicateSegmentCreate:
"""DuplicateSegment.create 测试"""
def test_create_success(self):
seg = DuplicateSegment.create(
source_start=10.0,
source_end=20.0,
matched_video_id="vid_123",
matched_video_name="测试视频",
matched_start=5.0,
matched_end=15.0,
similarity=85.5,
)
assert seg.id is not None
assert len(seg.id) == 32
assert seg.source_start == 10.0
assert seg.source_end == 20.0
assert seg.matched_video_id == "vid_123"
assert seg.matched_video_name == "测试视频"
assert seg.matched_start == 5.0
assert seg.matched_end == 15.0
assert seg.similarity == 85.5
def test_invalid_source_negative_start(self):
with pytest.raises(ValueError, match="invalid source segment range"):
DuplicateSegment.create(
source_start=-1.0,
source_end=10.0,
matched_video_id="v1",
matched_video_name="n1",
matched_start=0,
matched_end=10,
similarity=50,
)
def test_invalid_source_end_before_start(self):
with pytest.raises(ValueError, match="invalid source segment range"):
DuplicateSegment.create(
source_start=20.0,
source_end=10.0,
matched_video_id="v1",
matched_video_name="n1",
matched_start=0,
matched_end=10,
similarity=50,
)
def test_invalid_source_end_equals_start(self):
with pytest.raises(ValueError, match="invalid source segment range"):
DuplicateSegment.create(
source_start=10.0,
source_end=10.0,
matched_video_id="v1",
matched_video_name="n1",
matched_start=0,
matched_end=10,
similarity=50,
)
def test_invalid_matched_negative_start(self):
with pytest.raises(ValueError, match="invalid matched segment range"):
DuplicateSegment.create(
source_start=0,
source_end=10,
matched_video_id="v1",
matched_video_name="n1",
matched_start=-5,
matched_end=10,
similarity=50,
)
def test_invalid_matched_end_before_start(self):
with pytest.raises(ValueError, match="invalid matched segment range"):
DuplicateSegment.create(
source_start=0,
source_end=10,
matched_video_id="v1",
matched_video_name="n1",
matched_start=15,
matched_end=10,
similarity=50,
)
def test_invalid_similarity_negative(self):
with pytest.raises(ValueError, match="similarity must be between 0 and 100"):
DuplicateSegment.create(
source_start=0,
source_end=10,
matched_video_id="v1",
matched_video_name="n1",
matched_start=0,
matched_end=10,
similarity=-1,
)
def test_invalid_similarity_over_100(self):
with pytest.raises(ValueError, match="similarity must be between 0 and 100"):
DuplicateSegment.create(
source_start=0,
source_end=10,
matched_video_id="v1",
matched_video_name="n1",
matched_start=0,
matched_end=10,
similarity=101,
)
def test_similarity_boundary_zero(self):
seg = DuplicateSegment.create(
source_start=0,
source_end=10,
matched_video_id="v1",
matched_video_name="n1",
matched_start=0,
matched_end=10,
similarity=0,
)
assert seg.similarity == 0
def test_similarity_boundary_100(self):
seg = DuplicateSegment.create(
source_start=0,
source_end=10,
matched_video_id="v1",
matched_video_name="n1",
matched_start=0,
matched_end=10,
similarity=100,
)
assert seg.similarity == 100
class TestDuplicationRecordCreate:
"""DuplicationRecord.create 测试"""
def test_create_minimal(self):
record = DuplicationRecord.create(
user_id="user123",
filename="test.mp4",
file_size=1024000,
storage_key="oss://bucket/test.mp4",
)
assert record.id is not None
assert len(record.id) == 32
assert record.user_id == "user123"
assert record.filename == "test.mp4"
assert record.file_size == 1024000
assert record.storage_key == "oss://bucket/test.mp4"
assert record.status == "pending"
assert record.duplicate_rate is None
assert record.duplicate_count == 0
assert record.segments == []
assert record.duration_seconds == 0.0
assert record.created_at is not None
assert record.updated_at is not None
def test_create_with_duration(self):
record = DuplicationRecord.create(
user_id="u1",
filename="video.mp4",
file_size=5000,
storage_key="key",
duration_seconds=120.5,
)
assert record.duration_seconds == 120.5
def test_create_strips_whitespace(self):
record = DuplicationRecord.create(
user_id=" user456 ",
filename=" my video.mp4 ",
file_size=100,
storage_key="key",
)
assert record.user_id == "user456"
assert record.filename == "my video.mp4"
def test_empty_user_id_raises(self):
with pytest.raises(ValueError, match="user_id cannot be empty"):
DuplicationRecord.create(
user_id=" ",
filename="test.mp4",
file_size=100,
storage_key="key",
)
def test_empty_filename_raises(self):
with pytest.raises(ValueError, match="filename cannot be empty"):
DuplicationRecord.create(
user_id="u1",
filename=" ",
file_size=100,
storage_key="key",
)
def test_zero_file_size_raises(self):
with pytest.raises(ValueError, match="file_size must be positive"):
DuplicationRecord.create(
user_id="u1",
filename="test.mp4",
file_size=0,
storage_key="key",
)
def test_negative_file_size_raises(self):
with pytest.raises(ValueError, match="file_size must be positive"):
DuplicationRecord.create(
user_id="u1",
filename="test.mp4",
file_size=-100,
storage_key="key",
)
class TestDuplicationRecordLifecycle:
"""生命周期状态转换测试"""
def test_mark_processing(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
old_updated = record.updated_at
record.mark_processing()
assert record.status == "processing"
assert record.updated_at >= old_updated
def test_mark_completed(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
record.mark_processing()
segments = [
DuplicateSegment.create(
source_start=0,
source_end=10,
matched_video_id="v1",
matched_video_name="n1",
matched_start=0,
matched_end=10,
similarity=90,
)
]
record.mark_completed(
duplicate_rate=25.5,
duplicate_count=1,
segments=segments,
)
assert record.status == "completed"
assert record.duplicate_rate == 25.5
assert record.duplicate_count == 1
assert len(record.segments) == 1
assert record.error_message == ""
def test_mark_completed_zero_rate(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
record.mark_completed(duplicate_rate=0.0, duplicate_count=0, segments=[])
assert record.status == "completed"
assert record.duplicate_rate == 0.0
assert record.duplicate_count == 0
assert record.segments == []
def test_mark_completed_100_rate(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
record.mark_completed(duplicate_rate=100.0, duplicate_count=5, segments=[])
assert record.duplicate_rate == 100.0
def test_mark_completed_invalid_rate_negative(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
with pytest.raises(ValueError, match="duplicate_rate must be between 0 and 100"):
record.mark_completed(duplicate_rate=-1, duplicate_count=0, segments=[])
def test_mark_completed_invalid_rate_over_100(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
with pytest.raises(ValueError, match="duplicate_rate must be between 0 and 100"):
record.mark_completed(duplicate_rate=101, duplicate_count=0, segments=[])
def test_mark_failed(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
record.mark_processing()
record.mark_failed("网络超时")
assert record.status == "failed"
assert record.error_message == "网络超时"
assert record.duplicate_rate is None
def test_mark_failed_from_pending(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
record.mark_failed("文件损坏")
assert record.status == "failed"
assert record.error_message == "文件损坏"
class TestDuplicationRecordRetry:
"""重试逻辑测试"""
def test_can_retry_failed(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
record.mark_failed("error")
assert record.can_retry() is True
def test_cannot_retry_pending(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
assert record.can_retry() is False
def test_cannot_retry_processing(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
record.mark_processing()
assert record.can_retry() is False
def test_cannot_retry_completed(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
record.mark_completed(duplicate_rate=10, duplicate_count=1, segments=[])
assert record.can_retry() is False
def test_reset_for_retry(self):
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
record.mark_processing()
segments = [
DuplicateSegment.create(
source_start=0,
source_end=5,
matched_video_id="v1",
matched_video_name="n1",
matched_start=0,
matched_end=5,
similarity=80,
)
]
record.mark_completed(duplicate_rate=30, duplicate_count=1, segments=segments)
record.status = "failed"
record.error_message = "something wrong"
record.video_fingerprint = {"hash": "abc"}
record.reset_for_retry()
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.updated_at is not None
def test_reset_for_retry_from_pending(self):
"""即使从 pending 也能重置(调用方负责判断 can_retry"""
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k")
record.reset_for_retry()
assert record.status == "pending"
assert record.duplicate_count == 0