Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d42697ecc |
@@ -206,4 +206,167 @@ class TestClassificationJobCreate:
|
||||
|
||||
def test_create_id_is_hex(self):
|
||||
job = ClassificationJob.create(project_id="p", asset_id="a")
|
||||
int(job.id, 16) # 不抛异常就是合法 hex
|
||||
assert job.created_at.tzinfo is not None
|
||||
assert job.updated_at.tzinfo is not None
|
||||
|
||||
|
||||
class TestClassificationJobState:
|
||||
"""ClassificationJob 状态操作测试"""
|
||||
|
||||
def test_set_processing(self):
|
||||
job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
|
||||
job.status = ClassificationJobStatus.PROCESSING
|
||||
assert job.status == ClassificationJobStatus.PROCESSING
|
||||
|
||||
def test_set_completed_with_result(self):
|
||||
job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
|
||||
job.status = ClassificationJobStatus.COMPLETED
|
||||
job.classification = AssetClassification.SCENIC
|
||||
job.confidence = 0.95
|
||||
assert job.status == ClassificationJobStatus.COMPLETED
|
||||
assert job.classification == "scenic"
|
||||
assert job.confidence == pytest.approx(0.95)
|
||||
|
||||
def test_set_failed_with_error(self):
|
||||
job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
|
||||
job.status = ClassificationJobStatus.FAILED
|
||||
job.error_message = "model timeout"
|
||||
assert job.status == ClassificationJobStatus.FAILED
|
||||
assert job.error_message == "model timeout"
|
||||
|
||||
def test_confidence_range_zero(self):
|
||||
job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
|
||||
job.confidence = 0.0
|
||||
assert job.confidence == 0.0
|
||||
|
||||
def test_confidence_range_one(self):
|
||||
job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
|
||||
job.confidence = 1.0
|
||||
assert job.confidence == 1.0
|
||||
|
||||
|
||||
class TestClassificationJobStatusMissing:
|
||||
"""ClassificationJobStatus._missing_ 兼容行为测试"""
|
||||
|
||||
def test_done_maps_to_completed(self):
|
||||
assert ClassificationJobStatus("done") == ClassificationJobStatus.COMPLETED
|
||||
|
||||
def test_success_maps_to_completed(self):
|
||||
assert ClassificationJobStatus("success") == ClassificationJobStatus.COMPLETED
|
||||
|
||||
def test_finished_maps_to_completed(self):
|
||||
assert ClassificationJobStatus("finished") == ClassificationJobStatus.COMPLETED
|
||||
|
||||
def test_complete_maps_to_completed(self):
|
||||
assert ClassificationJobStatus("complete") == ClassificationJobStatus.COMPLETED
|
||||
|
||||
def test_fail_maps_to_failed(self):
|
||||
assert ClassificationJobStatus("fail") == ClassificationJobStatus.FAILED
|
||||
|
||||
def test_error_maps_to_failed(self):
|
||||
assert ClassificationJobStatus("error") == ClassificationJobStatus.FAILED
|
||||
|
||||
def test_err_maps_to_failed(self):
|
||||
assert ClassificationJobStatus("err") == ClassificationJobStatus.FAILED
|
||||
|
||||
def test_unknown_maps_to_pending(self):
|
||||
assert ClassificationJobStatus("unknown_status") == ClassificationJobStatus.PENDING
|
||||
|
||||
def test_case_insensitive_mapping(self):
|
||||
assert ClassificationJobStatus("DONE") == ClassificationJobStatus.COMPLETED
|
||||
assert ClassificationJobStatus("Done") == ClassificationJobStatus.COMPLETED
|
||||
|
||||
def test_whitespace_stripped(self):
|
||||
assert ClassificationJobStatus(" done ") == ClassificationJobStatus.COMPLETED
|
||||
|
||||
|
||||
class TestClassificationJobExtended:
|
||||
"""ClassificationJob 深度补充测试"""
|
||||
|
||||
def test_id_is_hex(self):
|
||||
job = ClassificationJob.create(project_id="p", asset_id="a")
|
||||
int(job.id, 16)
|
||||
|
||||
def test_empty_classification(self):
|
||||
job = ClassificationJob.create(project_id="p", asset_id="a")
|
||||
assert job.classification == ""
|
||||
|
||||
def test_zero_confidence(self):
|
||||
job = ClassificationJob.create(project_id="p", asset_id="a")
|
||||
assert job.confidence == 0.0
|
||||
|
||||
def test_high_confidence(self):
|
||||
job = ClassificationJob.create(project_id="p", asset_id="a")
|
||||
job.confidence = 0.99
|
||||
assert job.confidence == pytest.approx(0.99)
|
||||
|
||||
def test_negative_confidence(self):
|
||||
job = ClassificationJob.create(project_id="p", asset_id="a")
|
||||
job.confidence = -0.1
|
||||
assert job.confidence == pytest.approx(-0.1)
|
||||
|
||||
def test_confidence_over_one(self):
|
||||
job = ClassificationJob.create(project_id="p", asset_id="a")
|
||||
job.confidence = 1.5
|
||||
assert job.confidence == pytest.approx(1.5)
|
||||
|
||||
def test_empty_error_message(self):
|
||||
job = ClassificationJob.create(project_id="p", asset_id="a")
|
||||
assert job.error_message == ""
|
||||
|
||||
def test_long_error_message(self):
|
||||
job = ClassificationJob.create(project_id="p", asset_id="a")
|
||||
long_msg = "error" * 100
|
||||
job.error_message = long_msg
|
||||
assert job.error_message == long_msg
|
||||
assert len(job.error_message) == 500
|
||||
|
||||
def test_status_with_string_assignment(self):
|
||||
job = ClassificationJob.create(project_id="p", asset_id="a")
|
||||
job.status = "processing"
|
||||
assert job.status == ClassificationJobStatus.PROCESSING
|
||||
|
||||
|
||||
class TestAssetLibraryKindExtended:
|
||||
"""AssetLibraryKind 深度补充测试"""
|
||||
|
||||
def test_image_value(self):
|
||||
assert AssetLibraryKind.IMAGE == "image"
|
||||
|
||||
def test_all_three_kinds(self):
|
||||
assert len(AssetLibraryKind) == 3
|
||||
|
||||
def test_is_string_enum(self):
|
||||
assert isinstance(AssetLibraryKind.VIDEO, str)
|
||||
|
||||
def test_from_string(self):
|
||||
assert AssetLibraryKind("video") == AssetLibraryKind.VIDEO
|
||||
|
||||
|
||||
class TestIngestJobStatusExtended:
|
||||
"""IngestJobStatus 深度补充测试"""
|
||||
|
||||
def test_is_string_enum(self):
|
||||
assert isinstance(IngestJobStatus.PENDING, str)
|
||||
|
||||
def test_total_count(self):
|
||||
assert len(IngestJobStatus) == 4
|
||||
|
||||
def test_from_string(self):
|
||||
assert IngestJobStatus("pending") == IngestJobStatus.PENDING
|
||||
|
||||
|
||||
class TestAssetClassificationExtended:
|
||||
"""AssetClassification 深度补充测试"""
|
||||
|
||||
def test_total_count(self):
|
||||
assert len(AssetClassification) == 9
|
||||
|
||||
def test_is_string_enum(self):
|
||||
assert isinstance(AssetClassification.SCENIC, str)
|
||||
|
||||
def test_from_string(self):
|
||||
assert AssetClassification("scenic") == AssetClassification.SCENIC
|
||||
|
||||
def test_other_category(self):
|
||||
assert AssetClassification.OTHER == "other"
|
||||
|
||||
@@ -202,3 +202,139 @@ class TestTtsConfigClamp:
|
||||
config = TtsConfig.parse(data)
|
||||
assert isinstance(config.volume, float)
|
||||
assert config.volume == 1.0
|
||||
|
||||
|
||||
class TestTtsConfigTextEdge:
|
||||
"""文本字段边界测试."""
|
||||
|
||||
def test_long_text_preserved(self):
|
||||
long_text = "配音文本" * 500
|
||||
data = {"enabled": True, "voice_id": "v1", "text": long_text}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.text == long_text
|
||||
assert len(config.text) == 2000
|
||||
|
||||
def test_unicode_text_preserved(self):
|
||||
data = {"enabled": True, "voice_id": "v1", "text": "こんにちは世界🎵"}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.text == "こんにちは世界🎵"
|
||||
|
||||
def test_special_chars_text_preserved(self):
|
||||
data = {"enabled": True, "voice_id": "v1", "text": "line1\nline2\t tab <>&\"'"}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.text == "line1\nline2\t tab <>&\"'"
|
||||
|
||||
def test_empty_text_ok(self):
|
||||
data = {"enabled": True, "voice_id": "v1", "text": ""}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.text == ""
|
||||
|
||||
def test_text_none_fallback(self):
|
||||
data = {"enabled": True, "voice_id": "v1", "text": None}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.text == ""
|
||||
|
||||
|
||||
class TestTtsConfigVoiceIdEdge:
|
||||
"""voice_id 边界测试."""
|
||||
|
||||
def test_very_long_voice_id_preserved(self):
|
||||
long_id = "voice_" + "x" * 200
|
||||
data = {"enabled": True, "voice_id": long_id}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.voice_id == long_id
|
||||
|
||||
def test_voice_id_empty_string_ok(self):
|
||||
data = {"enabled": True, "voice_id": ""}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.voice_id == ""
|
||||
|
||||
def test_voice_id_unicode_ok(self):
|
||||
data = {"enabled": True, "voice_id": "音色_测试_001"}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.voice_id == "音色_测试_001"
|
||||
|
||||
|
||||
class TestTtsConfigClampEdge:
|
||||
"""钳制边界附近值测试."""
|
||||
|
||||
def test_speed_just_below_min_clamped(self):
|
||||
data = {"enabled": True, "speed": 0.499}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.speed == 0.5
|
||||
|
||||
def test_speed_just_above_max_clamped(self):
|
||||
data = {"enabled": True, "speed": 2.001}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.speed == 2.0
|
||||
|
||||
def test_pitch_just_below_min_clamped(self):
|
||||
data = {"enabled": True, "pitch": -12.1}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.pitch == -12
|
||||
|
||||
def test_pitch_just_above_max_clamped(self):
|
||||
data = {"enabled": True, "pitch": 12.1}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.pitch == 12
|
||||
|
||||
def test_volume_just_below_min_clamped(self):
|
||||
data = {"enabled": True, "volume": -0.001}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.volume == 0.0
|
||||
|
||||
def test_volume_just_above_max_clamped(self):
|
||||
data = {"enabled": True, "volume": 1.001}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.volume == 1.0
|
||||
|
||||
def test_direct_construct_clamp_speed(self):
|
||||
config = TtsConfig(enabled=True, speed=0.1)
|
||||
config._clamp()
|
||||
assert config.speed == 0.5
|
||||
|
||||
def test_direct_construct_clamp_pitch_volume(self):
|
||||
config = TtsConfig(enabled=True, pitch=-20, volume=2.0)
|
||||
config._clamp()
|
||||
assert config.pitch == -12
|
||||
assert config.volume == 1.0
|
||||
|
||||
|
||||
class TestTtsConfigAlignOverlapEdge:
|
||||
"""对齐与叠加模式边界."""
|
||||
|
||||
def test_align_mode_empty_string_fallback(self):
|
||||
data = {"enabled": True, "align_mode": ""}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.align_mode == "full"
|
||||
|
||||
def test_overlap_mode_empty_string_fallback(self):
|
||||
data = {"enabled": True, "overlap_mode": ""}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.overlap_mode == "replace"
|
||||
|
||||
def test_align_mode_case_sensitive(self):
|
||||
data = {"enabled": True, "align_mode": "SUBTITLE"}
|
||||
config = TtsConfig.parse(data)
|
||||
assert config.align_mode == "full"
|
||||
|
||||
|
||||
class TestTtsConfigEquality:
|
||||
"""相等性与独立性测试."""
|
||||
|
||||
def test_same_config_equal(self):
|
||||
c1 = TtsConfig(enabled=True, voice_id="v1", speed=1.5)
|
||||
c2 = TtsConfig(enabled=True, voice_id="v1", speed=1.5)
|
||||
assert c1 == c2
|
||||
|
||||
def test_different_config_not_equal(self):
|
||||
c1 = TtsConfig(enabled=True, voice_id="v1")
|
||||
c2 = TtsConfig(enabled=True, voice_id="v2")
|
||||
assert c1 != c2
|
||||
|
||||
def test_modify_one_does_not_affect_other(self):
|
||||
c1 = TtsConfig(enabled=True, voice_id="v1")
|
||||
c2 = TtsConfig(enabled=True, voice_id="v1")
|
||||
c2.speed = 2.0
|
||||
assert c1.speed == 1.0
|
||||
assert c1 != c2
|
||||
|
||||
Reference in New Issue
Block a user