From 1cdd5cab4605d1b83afcbd43f316864ca3953def Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 21 Jul 2026 19:35:45 +0800 Subject: [PATCH] =?UTF-8?q?test(unit):=20P3-1=20=E7=AC=AC=E5=9B=9B?= =?UTF-8?q?=E6=B3=A2=20=E6=96=B0=E5=A2=9E3=E4=B8=AA=E9=A2=86=E5=9F=9F?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95(65?= =?UTF-8?q?=E4=B8=AA=E7=94=A8=E4=BE=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - classification: 新增TestClassificationJobState状态操作测试 - generated_video: 新增TestGeneratedVideoProperties属性测试 - verification_code: 新增14个测试用例,覆盖创建/过期/使用/有效性/尝试次数/类型 已rebase到最新develop,解决与第三波的合并冲突 --- tests/unit/test_classification_domain.py | 35 ++++ tests/unit/test_generated_video_domain.py | 55 +++++++ tests/unit/test_verification_code_domain.py | 168 ++++++++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 tests/unit/test_verification_code_domain.py diff --git a/tests/unit/test_classification_domain.py b/tests/unit/test_classification_domain.py index f22089405..9b1edaa19 100755 --- a/tests/unit/test_classification_domain.py +++ b/tests/unit/test_classification_domain.py @@ -102,3 +102,38 @@ class TestClassificationJobCreate: job = ClassificationJob.create(project_id="p", asset_id="a") 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 diff --git a/tests/unit/test_generated_video_domain.py b/tests/unit/test_generated_video_domain.py index 915e63fba..d8e9fc42b 100755 --- a/tests/unit/test_generated_video_domain.py +++ b/tests/unit/test_generated_video_domain.py @@ -139,3 +139,58 @@ class TestGeneratedVideoCreate: video = GeneratedVideo.create(project_id="p", generation_task_id="t", name="n", file_url="u") assert video.created_at.tzinfo is not None assert video.generated_at.tzinfo is not None + + +class TestGeneratedVideoProperties: + """GeneratedVideo 属性测试""" + + def test_default_status_completed(self): + gv = GeneratedVideo.create( + project_id="proj-1", + generation_task_id="task-1", + name="测试视频", + file_url="https://example.com/video.mp4", + ) + assert gv.status == "completed" + + def test_default_review_status(self): + gv = GeneratedVideo.create( + project_id="proj-1", + generation_task_id="task-1", + name="测试视频", + file_url="https://example.com/video.mp4", + ) + assert gv.review_status == "pending_review" + + def test_set_status(self): + gv = GeneratedVideo.create( + project_id="proj-1", + generation_task_id="task-1", + name="测试视频", + file_url="https://example.com/video.mp4", + ) + gv.status = "failed" + assert gv.status == "failed" + + def test_mark_as_duplicate(self): + gv = GeneratedVideo.create( + project_id="proj-1", + generation_task_id="task-1", + name="测试视频", + file_url="https://example.com/video.mp4", + ) + gv.is_duplicate = True + gv.duplicate_of = "video-original" + assert gv.is_duplicate is True + assert gv.duplicate_of == "video-original" + + def test_set_fingerprint(self): + gv = GeneratedVideo.create( + project_id="proj-1", + generation_task_id="task-1", + name="测试视频", + file_url="https://example.com/video.mp4", + ) + fingerprint = {"phash": "abc123", "md5": "def456"} + gv.video_fingerprint = fingerprint + assert gv.video_fingerprint == fingerprint diff --git a/tests/unit/test_verification_code_domain.py b/tests/unit/test_verification_code_domain.py new file mode 100644 index 000000000..97a871ac5 --- /dev/null +++ b/tests/unit/test_verification_code_domain.py @@ -0,0 +1,168 @@ +""" +VerificationCode 验证码领域模型单元测试 +""" + +from datetime import datetime, timedelta, timezone + +import pytest +from domain.verification_code import VerificationCode + + +class TestVerificationCodeCreate: + """创建验证码测试""" + + def test_create_default_6digit_code(self): + vc = VerificationCode.create("test@example.com", "email_bind") + assert vc.id is not None + assert len(vc.id) == 32 # uuid4 hex + assert vc.recipient == "test@example.com" + assert vc.code_type == "email_bind" + assert len(vc.code) == 6 + assert vc.code.isdigit() + assert vc.used_at is None + assert vc.attempts == 0 + + def test_create_custom_code(self): + vc = VerificationCode.create("13800138000", "phone_bind", custom_code="123456") + assert vc.code == "123456" + + def test_create_default_ttl_300s(self): + before = datetime.now(timezone.utc) + vc = VerificationCode.create("test@example.com", "email_login") + after = datetime.now(timezone.utc) + expected_expiry_min = before + timedelta(seconds=300) + expected_expiry_max = after + timedelta(seconds=300) + assert expected_expiry_min <= vc.expires_at <= expected_expiry_max + + def test_create_custom_ttl(self): + vc = VerificationCode.create("test@example.com", "reset_password", ttl_seconds=60) + expected = datetime.now(timezone.utc) + timedelta(seconds=60) + diff = abs((vc.expires_at - expected).total_seconds()) + assert diff < 2 + + def test_create_recipient_stripped(self): + vc = VerificationCode.create(" test@example.com ", "email_bind") + assert vc.recipient == "test@example.com" + + def test_create_phone_recipient(self): + vc = VerificationCode.create("13800138000", "phone_login") + assert vc.recipient == "13800138000" + assert vc.code_type == "phone_login" + + def test_create_sets_created_at(self): + vc = VerificationCode.create("test@example.com", "email_bind") + assert vc.created_at is not None + assert isinstance(vc.created_at, datetime) + + +class TestVerificationCodeExpiry: + """过期状态测试""" + + def test_fresh_code_not_expired(self): + vc = VerificationCode.create("test@example.com", "email_bind") + assert vc.is_expired is False + + def test_expired_code_is_expired(self): + vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=-60) + assert vc.is_expired is True + + def test_boundary_not_expired_at_expiry_time(self): + now = datetime.now(timezone.utc) + vc = VerificationCode.create("test@example.com", "email_bind") + vc.expires_at = now + timedelta(seconds=1) + assert vc.is_expired is False + + def test_boundary_expired_right_after(self): + vc = VerificationCode.create("test@example.com", "email_bind") + vc.expires_at = datetime.now(timezone.utc) - timedelta(microseconds=1) + assert vc.is_expired is True + + +class TestVerificationCodeUsed: + """使用状态测试""" + + def test_fresh_code_not_used(self): + vc = VerificationCode.create("test@example.com", "email_bind") + assert vc.is_used is False + + def test_mark_used(self): + vc = VerificationCode.create("test@example.com", "email_bind") + vc.mark_used() + assert vc.is_used is True + assert vc.used_at is not None + assert isinstance(vc.used_at, datetime) + + def test_mark_used_sets_recent_time(self): + vc = VerificationCode.create("test@example.com", "email_bind") + before = datetime.now(timezone.utc) + vc.mark_used() + after = datetime.now(timezone.utc) + assert before <= vc.used_at <= after + + def test_mark_used_idempotent(self): + vc = VerificationCode.create("test@example.com", "email_bind") + vc.mark_used() + first_used_at = vc.used_at + vc.mark_used() + # 第二次会更新时间 + assert vc.used_at >= first_used_at + + +class TestVerificationCodeValidity: + """有效性(未过期+未使用)测试""" + + def test_fresh_code_is_valid(self): + vc = VerificationCode.create("test@example.com", "email_bind") + assert vc.is_valid is True + + def test_expired_code_not_valid(self): + vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=-10) + assert vc.is_valid is False + + def test_used_code_not_valid(self): + vc = VerificationCode.create("test@example.com", "email_bind") + vc.mark_used() + assert vc.is_valid is False + + def test_expired_and_used_not_valid(self): + vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=-10) + vc.mark_used() + assert vc.is_valid is False + + +class TestVerificationCodeAttempts: + """尝试次数测试""" + + def test_initial_attempts_zero(self): + vc = VerificationCode.create("test@example.com", "email_bind") + assert vc.attempts == 0 + + def test_increment_attempts(self): + vc = VerificationCode.create("test@example.com", "email_bind") + vc.increment_attempts() + assert vc.attempts == 1 + + def test_increment_attempts_multiple(self): + vc = VerificationCode.create("test@example.com", "email_bind") + for _ in range(5): + vc.increment_attempts() + assert vc.attempts == 5 + + +class TestVerificationCodeTypes: + """不同验证码类型测试""" + + @pytest.mark.parametrize( + "code_type", + [ + "email_bind", + "phone_bind", + "email_login", + "phone_login", + "reset_password", + ], + ) + def test_all_supported_types(self, code_type): + vc = VerificationCode.create("test@example.com", code_type) + assert vc.code_type == code_type + assert vc.is_valid is True -- 2.54.0