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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
352 lines
13 KiB
Python
Executable File
352 lines
13 KiB
Python
Executable File
"""
|
||
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
|