""" 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