diff --git a/tests/unit/test_classification_domain.py b/tests/unit/test_classification_domain.py index ef46fc511..5219983f9 100755 --- a/tests/unit/test_classification_domain.py +++ b/tests/unit/test_classification_domain.py @@ -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" diff --git a/tests/unit/test_tts_config_domain.py b/tests/unit/test_tts_config_domain.py index 61f068411..0c2087a2f 100755 --- a/tests/unit/test_tts_config_domain.py +++ b/tests/unit/test_tts_config_domain.py @@ -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