71faf2ba44
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 44s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m10s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m11s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m48s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m33s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m52s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m28s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 2m0s
CI/CD Pipeline / Unit Tests (push) Failing after 6m22s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m27s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m15s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 32s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m53s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m2s
324 lines
11 KiB
Python
Executable File
324 lines
11 KiB
Python
Executable File
"""GeneratedVideo + VerificationCode 领域模型测试."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
|
|
import pytest
|
|
|
|
from packages.domain.generated_video import GeneratedVideo
|
|
from packages.domain.verification_code import VerificationCode
|
|
|
|
|
|
class TestGeneratedVideo:
|
|
"""GeneratedVideo 生成视频实体测试."""
|
|
|
|
def test_create_success(self):
|
|
"""创建成功."""
|
|
video = GeneratedVideo.create(
|
|
project_id="p1",
|
|
generation_task_id="task_1",
|
|
name="我的视频.mp4",
|
|
file_url="https://example.com/out.mp4",
|
|
user_id="u1",
|
|
file_size=1024000,
|
|
duration=30.5,
|
|
width=1080,
|
|
height=1920,
|
|
fps=25.0,
|
|
)
|
|
assert video.id is not None
|
|
assert len(video.id) == 32
|
|
assert video.project_id == "p1"
|
|
assert video.generation_task_id == "task_1"
|
|
assert video.name == "我的视频.mp4"
|
|
assert video.file_url == "https://example.com/out.mp4"
|
|
assert video.user_id == "u1"
|
|
assert video.file_size == 1024000
|
|
assert video.duration == pytest.approx(30.5)
|
|
assert video.width == 1080
|
|
assert video.height == 1920
|
|
assert video.fps == pytest.approx(25.0)
|
|
assert video.status == "completed"
|
|
assert video.review_status == "pending_review"
|
|
assert video.is_duplicate is False
|
|
assert video.duplicate_of is None
|
|
assert video.generation_params == {}
|
|
|
|
def test_create_empty_project_id_raises(self):
|
|
"""空project_id抛异常."""
|
|
with pytest.raises(ValueError, match="project_id"):
|
|
GeneratedVideo.create(
|
|
project_id=" ",
|
|
generation_task_id="t1",
|
|
name="v.mp4",
|
|
file_url="https://x.com/v.mp4",
|
|
)
|
|
|
|
def test_create_empty_task_id_raises(self):
|
|
"""空generation_task_id抛异常."""
|
|
with pytest.raises(ValueError, match="generation_task_id"):
|
|
GeneratedVideo.create(
|
|
project_id="p1",
|
|
generation_task_id="",
|
|
name="v.mp4",
|
|
file_url="https://x.com/v.mp4",
|
|
)
|
|
|
|
def test_create_empty_name_raises(self):
|
|
"""空name抛异常."""
|
|
with pytest.raises(ValueError, match="name"):
|
|
GeneratedVideo.create(
|
|
project_id="p1",
|
|
generation_task_id="t1",
|
|
name=" ",
|
|
file_url="https://x.com/v.mp4",
|
|
)
|
|
|
|
def test_create_empty_file_url_raises(self):
|
|
"""空file_url抛异常."""
|
|
with pytest.raises(ValueError, match="file_url"):
|
|
GeneratedVideo.create(
|
|
project_id="p1",
|
|
generation_task_id="t1",
|
|
name="v.mp4",
|
|
file_url="",
|
|
)
|
|
|
|
def test_create_strips_whitespace(self):
|
|
"""首尾空白被去除."""
|
|
video = GeneratedVideo.create(
|
|
project_id=" p1 ",
|
|
generation_task_id=" t1 ",
|
|
name=" 视频.mp4 ",
|
|
file_url=" https://x.com/v.mp4 ",
|
|
)
|
|
assert video.project_id == "p1"
|
|
assert video.generation_task_id == "t1"
|
|
assert video.name == "视频.mp4"
|
|
assert video.file_url == "https://x.com/v.mp4"
|
|
|
|
def test_default_values(self):
|
|
"""默认值正确."""
|
|
video = GeneratedVideo.create(
|
|
project_id="p1",
|
|
generation_task_id="t1",
|
|
name="v.mp4",
|
|
file_url="https://x.com/v.mp4",
|
|
)
|
|
assert video.user_id == ""
|
|
assert video.file_size == 0
|
|
assert video.duration == 0.0
|
|
assert video.width == 0
|
|
assert video.height == 0
|
|
assert video.fps == 0.0
|
|
assert video.thumbnail_url is None
|
|
assert video.generation_params == {}
|
|
|
|
def test_generation_params_none_becomes_empty(self):
|
|
"""generation_params=None → {}."""
|
|
video = GeneratedVideo.create(
|
|
project_id="p1",
|
|
generation_task_id="t1",
|
|
name="v.mp4",
|
|
file_url="https://x.com/v.mp4",
|
|
generation_params=None,
|
|
)
|
|
assert video.generation_params == {}
|
|
|
|
def test_custom_generation_params(self):
|
|
"""自定义生成参数."""
|
|
params = {"mode": "smart", "resolution": "1080x1920"}
|
|
video = GeneratedVideo.create(
|
|
project_id="p1",
|
|
generation_task_id="t1",
|
|
name="v.mp4",
|
|
file_url="https://x.com/v.mp4",
|
|
generation_params=params,
|
|
)
|
|
assert video.generation_params == params
|
|
|
|
def test_duplicate_flag(self):
|
|
"""重复标记可以设置."""
|
|
video = GeneratedVideo.create(
|
|
project_id="p1",
|
|
generation_task_id="t1",
|
|
name="v.mp4",
|
|
file_url="https://x.com/v.mp4",
|
|
)
|
|
video.is_duplicate = True
|
|
video.duplicate_of = "other_video_id"
|
|
assert video.is_duplicate is True
|
|
assert video.duplicate_of == "other_video_id"
|
|
|
|
def test_custom_status(self):
|
|
"""自定义状态."""
|
|
video = GeneratedVideo.create(
|
|
project_id="p1",
|
|
generation_task_id="t1",
|
|
name="v.mp4",
|
|
file_url="https://x.com/v.mp4",
|
|
)
|
|
video.status = "failed"
|
|
assert video.status == "failed"
|
|
|
|
def test_thumbnail_url(self):
|
|
"""缩略图URL."""
|
|
video = GeneratedVideo.create(
|
|
project_id="p1",
|
|
generation_task_id="t1",
|
|
name="v.mp4",
|
|
file_url="https://x.com/v.mp4",
|
|
thumbnail_url="https://x.com/thumb.jpg",
|
|
)
|
|
assert video.thumbnail_url == "https://x.com/thumb.jpg"
|
|
|
|
|
|
class TestVerificationCodeCreate:
|
|
"""VerificationCode 创建测试."""
|
|
|
|
def test_create_success(self):
|
|
"""创建验证码成功."""
|
|
vc = VerificationCode.create(
|
|
recipient="test@example.com",
|
|
code_type="email_bind",
|
|
ttl_seconds=300,
|
|
)
|
|
assert vc.id is not None
|
|
assert len(vc.id) == 32
|
|
assert vc.recipient == "test@example.com"
|
|
assert vc.code_type == "email_bind"
|
|
assert len(vc.code) == 6 # 默认6位数字
|
|
assert vc.code.isdigit() # 纯数字
|
|
assert vc.used_at is None
|
|
assert vc.attempts == 0
|
|
|
|
def test_create_with_custom_code(self):
|
|
"""自定义验证码."""
|
|
vc = VerificationCode.create(
|
|
recipient="u@test.com",
|
|
code_type="email_login",
|
|
custom_code="123456",
|
|
)
|
|
assert vc.code == "123456"
|
|
|
|
def test_create_recipient_stripped(self):
|
|
"""收件人空白被去除."""
|
|
vc = VerificationCode.create(
|
|
recipient=" test@example.com ",
|
|
code_type="email_bind",
|
|
)
|
|
assert vc.recipient == "test@example.com"
|
|
|
|
def test_expiry_time_correct(self):
|
|
"""过期时间正确(5分钟后)."""
|
|
from datetime import datetime, timezone
|
|
|
|
before = datetime.now(timezone.utc) + timedelta(seconds=299)
|
|
vc = VerificationCode.create(
|
|
recipient="u@test.com",
|
|
code_type="reset_password",
|
|
ttl_seconds=300,
|
|
)
|
|
after = datetime.now(timezone.utc) + timedelta(seconds=301)
|
|
assert before <= vc.expires_at <= after
|
|
|
|
def test_custom_ttl(self):
|
|
"""自定义过期时间."""
|
|
vc = VerificationCode.create(
|
|
recipient="u@test.com",
|
|
code_type="phone_login",
|
|
ttl_seconds=60,
|
|
)
|
|
from datetime import datetime, timezone
|
|
|
|
now = datetime.now(timezone.utc)
|
|
# 应该在1分钟左右过期
|
|
diff = (vc.expires_at - now).total_seconds()
|
|
assert 0 < diff < 70
|
|
|
|
|
|
class TestVerificationCodeProperties:
|
|
"""VerificationCode 属性方法测试."""
|
|
|
|
def test_is_expired_false_for_new(self):
|
|
"""新创建的验证码未过期."""
|
|
vc = VerificationCode.create(
|
|
recipient="u@test.com",
|
|
code_type="email_bind",
|
|
ttl_seconds=300,
|
|
)
|
|
assert vc.is_expired is False
|
|
|
|
def test_is_expired_true_when_past(self):
|
|
"""已过期的验证码is_expired=True."""
|
|
vc = VerificationCode.create(
|
|
recipient="u@test.com",
|
|
code_type="email_bind",
|
|
ttl_seconds=1,
|
|
)
|
|
# 手动改过期时间到过去
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
vc.expires_at = datetime.now(timezone.utc) - timedelta(seconds=1)
|
|
assert vc.is_expired is True
|
|
|
|
def test_is_used_false_by_default(self):
|
|
"""默认未使用."""
|
|
vc = VerificationCode.create(recipient="u@test.com", code_type="email_bind")
|
|
assert vc.is_used is False
|
|
|
|
def test_is_valid_fresh_code(self):
|
|
"""新验证码有效."""
|
|
vc = VerificationCode.create(recipient="u@test.com", code_type="email_bind")
|
|
assert vc.is_valid is True
|
|
|
|
def test_is_valid_expired(self):
|
|
"""过期的验证码无效."""
|
|
vc = VerificationCode.create(recipient="u@test.com", code_type="email_bind", ttl_seconds=1)
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
vc.expires_at = datetime.now(timezone.utc) - timedelta(seconds=1)
|
|
assert vc.is_valid is False
|
|
|
|
def test_is_valid_used(self):
|
|
"""已使用的验证码无效."""
|
|
vc = VerificationCode.create(recipient="u@test.com", code_type="email_bind")
|
|
vc.mark_used()
|
|
assert vc.is_valid is False
|
|
|
|
|
|
class TestVerificationCodeActions:
|
|
"""VerificationCode 操作方法测试."""
|
|
|
|
def test_mark_used(self):
|
|
"""标记使用."""
|
|
vc = VerificationCode.create(recipient="u@test.com", code_type="email_bind")
|
|
vc.mark_used()
|
|
assert vc.is_used is True
|
|
assert vc.used_at is not None
|
|
|
|
def test_mark_used_twice(self):
|
|
"""标记两次也没问题."""
|
|
vc = VerificationCode.create(recipient="u@test.com", code_type="email_bind")
|
|
vc.mark_used()
|
|
first_time = vc.used_at
|
|
vc.mark_used()
|
|
# 第二次会覆盖时间
|
|
assert vc.used_at >= first_time
|
|
|
|
def test_increment_attempts(self):
|
|
"""增加尝试次数."""
|
|
vc = VerificationCode.create(recipient="u@test.com", code_type="email_bind")
|
|
assert vc.attempts == 0
|
|
vc.increment_attempts()
|
|
assert vc.attempts == 1
|
|
vc.increment_attempts()
|
|
assert vc.attempts == 2
|
|
|
|
def test_all_code_types_supported(self):
|
|
"""支持所有code_type."""
|
|
for code_type in ["email_bind", "phone_bind", "email_login", "phone_login", "reset_password"]:
|
|
vc = VerificationCode.create(recipient="u@test.com", code_type=code_type)
|
|
assert vc.code_type == code_type
|