test: 第82波单测 - exceptions/template/edit_template/generated_video (+86)
AI Code Review / AI Code Review (pull_request) Failing after 0s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 18s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 56s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 48s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 28s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 35s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 29s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 16s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 3m51s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m24s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m13s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m7s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m2s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 48m5s
AI Code Review / AI Code Review (pull_request) Failing after 0s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 18s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 56s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 48s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 28s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 35s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 29s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 16s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 3m51s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m24s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m13s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m7s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m2s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 48m5s
新增86个测试: - test_domain_exceptions +20(新建:DomainError/NotFoundError/ValidationError/QuotaExceededError) - test_template_domain +28(tags/config独立性、零值大值负值、长名称unicode、多segments等) - test_template_version_domain +16(id hex/唯一性、零值负值大值version、复杂config等) - test_edit_template_domain +22(id hex/唯一性、长描述、sort_weight边界、状态切换等)
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
"""领域层通用异常单元测试."""
|
||||
"""
|
||||
领域层异常类单元测试
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -14,118 +16,99 @@ class TestDomainError:
|
||||
"""DomainError 基类测试"""
|
||||
|
||||
def test_is_exception(self):
|
||||
"""DomainError 是 Exception 的子类"""
|
||||
assert issubclass(DomainError, Exception)
|
||||
|
||||
def test_can_raise_and_catch(self):
|
||||
"""可以抛出和捕获"""
|
||||
def test_raise_and_catch(self):
|
||||
with pytest.raises(DomainError):
|
||||
raise DomainError("something went wrong")
|
||||
raise DomainError("test error")
|
||||
|
||||
def test_message(self):
|
||||
"""异常消息正确"""
|
||||
err = DomainError("test message")
|
||||
assert str(err) == "test message"
|
||||
def test_error_message(self):
|
||||
err = DomainError("something went wrong")
|
||||
assert str(err) == "something went wrong"
|
||||
|
||||
def test_empty_message(self):
|
||||
err = DomainError("")
|
||||
assert str(err) == ""
|
||||
|
||||
|
||||
class TestNotFoundError:
|
||||
"""NotFoundError 测试"""
|
||||
|
||||
def test_is_domain_error(self):
|
||||
"""NotFoundError 继承自 DomainError"""
|
||||
assert issubclass(NotFoundError, DomainError)
|
||||
|
||||
def test_can_raise_as_domain_error(self):
|
||||
"""可以作为 DomainError 捕获"""
|
||||
def test_raise_and_catch_as_domain(self):
|
||||
with pytest.raises(DomainError):
|
||||
raise NotFoundError("resource not found")
|
||||
|
||||
def test_default_message(self):
|
||||
"""无参构造"""
|
||||
err = NotFoundError()
|
||||
assert isinstance(err, NotFoundError)
|
||||
def test_raise_and_catch_specific(self):
|
||||
with pytest.raises(NotFoundError):
|
||||
raise NotFoundError("not found")
|
||||
|
||||
def test_custom_message(self):
|
||||
"""自定义消息"""
|
||||
def test_error_message(self):
|
||||
err = NotFoundError("user 123 not found")
|
||||
assert str(err) == "user 123 not found"
|
||||
assert "user 123 not found" in str(err)
|
||||
|
||||
|
||||
class TestValidationError:
|
||||
"""ValidationError 测试"""
|
||||
|
||||
def test_is_domain_error(self):
|
||||
"""ValidationError 继承自 DomainError"""
|
||||
assert issubclass(ValidationError, DomainError)
|
||||
|
||||
def test_can_raise_as_domain_error(self):
|
||||
"""可以作为 DomainError 捕获"""
|
||||
def test_raise_and_catch_as_domain(self):
|
||||
with pytest.raises(DomainError):
|
||||
raise ValidationError("invalid input")
|
||||
|
||||
def test_custom_message(self):
|
||||
"""自定义消息"""
|
||||
err = ValidationError("duration must be positive")
|
||||
assert str(err) == "duration must be positive"
|
||||
def test_raise_and_catch_specific(self):
|
||||
with pytest.raises(ValidationError):
|
||||
raise ValidationError("validation failed")
|
||||
|
||||
def test_error_message(self):
|
||||
err = ValidationError("name cannot be empty")
|
||||
assert "name cannot be empty" in str(err)
|
||||
|
||||
|
||||
class TestQuotaExceededError:
|
||||
"""QuotaExceededError 测试"""
|
||||
|
||||
def test_is_domain_error(self):
|
||||
"""QuotaExceededError 继承自 DomainError"""
|
||||
assert issubclass(QuotaExceededError, DomainError)
|
||||
|
||||
def test_can_raise_as_domain_error(self):
|
||||
"""可以作为 DomainError 捕获"""
|
||||
with pytest.raises(DomainError):
|
||||
raise QuotaExceededError("storage", 1024.0, 2048.0)
|
||||
|
||||
def test_stores_dimension_limit_used(self):
|
||||
"""保存 dimension、limit、used 属性"""
|
||||
err = QuotaExceededError("storage_mb", 1024.0, 1500.0)
|
||||
assert err.dimension == "storage_mb"
|
||||
def test_constructor_sets_attributes(self):
|
||||
err = QuotaExceededError(dimension="storage", limit=1024.0, used=2048.0)
|
||||
assert err.dimension == "storage"
|
||||
assert err.limit == 1024.0
|
||||
assert err.used == 1500.0
|
||||
assert err.used == 2048.0
|
||||
|
||||
def test_error_message_format(self):
|
||||
"""异常消息格式正确"""
|
||||
err = QuotaExceededError("storage_mb", 1024.0, 1500.0)
|
||||
err = QuotaExceededError(dimension="storage", limit=1024.0, used=2048.0)
|
||||
msg = str(err)
|
||||
assert "storage_mb" in msg
|
||||
assert "1500.0" in msg
|
||||
assert "storage" in msg
|
||||
assert "2048.0" in msg
|
||||
assert "1024.0" in msg
|
||||
assert "Quota exceeded" in msg
|
||||
|
||||
def test_integer_values(self):
|
||||
"""整数值也能正常工作"""
|
||||
err = QuotaExceededError("projects", 10, 15)
|
||||
assert err.dimension == "projects"
|
||||
assert err.limit == 10
|
||||
assert err.used == 15
|
||||
def test_raise_and_catch_as_domain(self):
|
||||
with pytest.raises(DomainError):
|
||||
raise QuotaExceededError("projects", 10, 15)
|
||||
|
||||
def test_raise_and_catch_specific(self):
|
||||
with pytest.raises(QuotaExceededError):
|
||||
raise QuotaExceededError("render", 5, 10)
|
||||
|
||||
def test_zero_limit(self):
|
||||
"""限制为 0 时也能正常工作"""
|
||||
err = QuotaExceededError("custom_templates", 0, 1)
|
||||
assert err.limit == 0
|
||||
assert err.used == 1
|
||||
err = QuotaExceededError(dimension="test", limit=0.0, used=1.0)
|
||||
assert err.limit == 0.0
|
||||
assert err.used == 1.0
|
||||
|
||||
def test_negative_values(self):
|
||||
"""负数也能存(领域层不做额外校验)"""
|
||||
err = QuotaExceededError(dimension="test", limit=-5.0, used=-3.0)
|
||||
assert err.limit == -5.0
|
||||
assert err.used == -3.0
|
||||
|
||||
class TestExceptionHierarchy:
|
||||
"""异常继承关系测试"""
|
||||
|
||||
def test_all_are_domain_errors(self):
|
||||
"""所有异常都可以作为 DomainError 捕获"""
|
||||
errors = [
|
||||
NotFoundError(),
|
||||
ValidationError("bad"),
|
||||
QuotaExceededError("x", 10.0, 20.0),
|
||||
]
|
||||
for err in errors:
|
||||
assert isinstance(err, DomainError)
|
||||
|
||||
def test_distinct_types(self):
|
||||
"""不同异常类型可以区分"""
|
||||
assert not issubclass(NotFoundError, ValidationError)
|
||||
assert not issubclass(ValidationError, QuotaExceededError)
|
||||
assert not issubclass(NotFoundError, QuotaExceededError)
|
||||
def test_large_values(self):
|
||||
err = QuotaExceededError(dimension="storage", limit=1e9, used=1.5e9)
|
||||
assert err.limit == 1e9
|
||||
assert err.used == 1.5e9
|
||||
|
||||
@@ -152,3 +152,103 @@ class TestEditTemplateBumpVersion:
|
||||
old_updated = template.updated_at
|
||||
template.bump_version()
|
||||
assert template.updated_at > old_updated or template.updated_at == old_updated
|
||||
|
||||
|
||||
class TestEditTemplateExtended:
|
||||
"""EditTemplate 深度补充测试"""
|
||||
|
||||
def test_id_is_hex(self):
|
||||
t = EditTemplate.create(name="test")
|
||||
int(t.id, 16)
|
||||
|
||||
def test_ids_are_unique(self):
|
||||
t1 = EditTemplate.create(name="test1")
|
||||
t2 = EditTemplate.create(name="test2")
|
||||
assert t1.id != t2.id
|
||||
|
||||
def test_empty_description(self):
|
||||
t = EditTemplate.create(name="test", description="")
|
||||
assert t.description == ""
|
||||
|
||||
def test_long_description(self):
|
||||
desc = "描述" * 200
|
||||
t = EditTemplate.create(name="test", description=desc)
|
||||
assert t.description == desc
|
||||
assert len(t.description) == 400
|
||||
|
||||
def test_unicode_name(self):
|
||||
t = EditTemplate.create(name="🎬 口播 Vlog 模板")
|
||||
assert "🎬" in t.name
|
||||
assert "口播" in t.name
|
||||
|
||||
def test_special_characters_name(self):
|
||||
special = "模!@#$%板"
|
||||
t = EditTemplate.create(name=special)
|
||||
assert t.name == special
|
||||
|
||||
def test_long_name(self):
|
||||
long_name = "模板名称" * 50
|
||||
t = EditTemplate.create(name=long_name)
|
||||
assert t.name == long_name
|
||||
assert len(t.name) == 200
|
||||
|
||||
def test_config_independence(self):
|
||||
t1 = EditTemplate.create(name="test1")
|
||||
t2 = EditTemplate.create(name="test2")
|
||||
t1.config["key"] = "val"
|
||||
assert "key" not in t2.config
|
||||
|
||||
def test_sort_weight_negative(self):
|
||||
t = EditTemplate.create(name="test", sort_weight=-100)
|
||||
assert t.sort_weight == -100
|
||||
|
||||
def test_sort_weight_large(self):
|
||||
t = EditTemplate.create(name="test", sort_weight=99999)
|
||||
assert t.sort_weight == 99999
|
||||
|
||||
def test_sort_weight_zero(self):
|
||||
t = EditTemplate.create(name="test", sort_weight=0)
|
||||
assert t.sort_weight == 0
|
||||
|
||||
def test_preview_url_empty(self):
|
||||
t = EditTemplate.create(name="test", preview_url="")
|
||||
assert t.preview_url == ""
|
||||
|
||||
def test_version_zero(self):
|
||||
t = EditTemplate.create(name="test", version=0)
|
||||
assert t.version == 0
|
||||
|
||||
def test_version_large(self):
|
||||
t = EditTemplate.create(name="test", version=999)
|
||||
assert t.version == 999
|
||||
|
||||
def test_status_is_active_property(self):
|
||||
t = EditTemplate.create(name="test", status=EditTemplateStatus.ACTIVE)
|
||||
assert t.is_active is True
|
||||
t.deactivate()
|
||||
assert t.is_active is False
|
||||
t.activate()
|
||||
assert t.is_active is True
|
||||
|
||||
def test_bump_version_from_zero(self):
|
||||
t = EditTemplate.create(name="test", version=0)
|
||||
t.bump_version()
|
||||
assert t.version == 1
|
||||
|
||||
def test_template_type_custom(self):
|
||||
t = EditTemplate.create(name="test", template_type="custom_type")
|
||||
assert t.template_type == "custom_type"
|
||||
|
||||
def test_template_type_strips_and_default(self):
|
||||
"""空格的 template_type 回退到 default"""
|
||||
t = EditTemplate.create(name="test", template_type=" ")
|
||||
assert t.template_type == "default"
|
||||
|
||||
def test_create_with_empty_editing_mode_defaults(self):
|
||||
"""空字符串 editing_mode 回退到 one_take"""
|
||||
t = EditTemplate.create(name="test", editing_mode="")
|
||||
assert t.editing_mode == "one_take"
|
||||
|
||||
def test_preview_url_strips_whitespace(self):
|
||||
t = EditTemplate.create(name="test", preview_url=" https://example.com/v.mp4 ")
|
||||
assert t.preview_url == "https://example.com/v.mp4"
|
||||
|
||||
Regular → Executable
+139
@@ -151,3 +151,142 @@ class TestTemplateCategory:
|
||||
def test_category_has_timestamp(self):
|
||||
cat = TemplateCategory(id="cat-1", user_id="u1", name="风景")
|
||||
assert cat.created_at is not None
|
||||
|
||||
|
||||
class TestTemplateExtended:
|
||||
"""Template 深度补充测试"""
|
||||
|
||||
def test_tags_independence(self):
|
||||
"""不同模板的 tags 列表互不影响"""
|
||||
t1 = Template(id="t1", user_id="u1", name="n1", mode="one_take")
|
||||
t2 = Template(id="t2", user_id="u1", name="n2", mode="one_take")
|
||||
t1.tags.append("新标签")
|
||||
assert "新标签" not in t2.tags
|
||||
assert len(t2.tags) == 0
|
||||
|
||||
def test_title_config_independence(self):
|
||||
t1 = Template(id="t1", user_id="u1", name="n1", mode="one_take")
|
||||
t2 = Template(id="t2", user_id="u1", name="n2", mode="one_take")
|
||||
t1.title_config["key"] = "val"
|
||||
assert "key" not in t2.title_config
|
||||
|
||||
def test_subtitle_config_independence(self):
|
||||
t1 = Template(id="t1", user_id="u1", name="n1", mode="one_take")
|
||||
t2 = Template(id="t2", user_id="u1", name="n2", mode="one_take")
|
||||
t1.subtitle_config["key"] = "val"
|
||||
assert "key" not in t2.subtitle_config
|
||||
|
||||
def test_bgm_config_independence(self):
|
||||
t1 = Template(id="t1", user_id="u1", name="n1", mode="one_take")
|
||||
t2 = Template(id="t2", user_id="u1", name="n2", mode="one_take")
|
||||
t1.bgm_config["key"] = "val"
|
||||
assert "key" not in t2.bgm_config
|
||||
|
||||
def test_segments_independence(self):
|
||||
segs = [
|
||||
TemplateSegment(id="s1", template_id="t1", segment_order=0, duration_min=1, duration_max=2),
|
||||
]
|
||||
t1 = Template(id="t1", user_id="u1", name="n1", mode="one_take", segments=segs)
|
||||
t2 = Template(id="t2", user_id="u1", name="n2", mode="one_take")
|
||||
assert len(t2.segments) == 0
|
||||
|
||||
def test_empty_segments(self):
|
||||
t = Template(id="t1", user_id="u1", name="n", mode="one_take", segments=[])
|
||||
assert t.segments == []
|
||||
|
||||
def test_many_segments(self):
|
||||
segs = [
|
||||
TemplateSegment(id=f"s{i}", template_id="t1", segment_order=i, duration_min=1, duration_max=3)
|
||||
for i in range(30)
|
||||
]
|
||||
t = Template(id="t1", user_id="u1", name="n", mode="one_take", segments=segs)
|
||||
assert len(t.segments) == 30
|
||||
assert t.segments[0].segment_order == 0
|
||||
assert t.segments[29].segment_order == 29
|
||||
|
||||
def test_zero_estimated_duration(self):
|
||||
t = Template(id="t1", user_id="u1", name="n", mode="one_take", estimated_duration=0.0)
|
||||
assert t.estimated_duration == 0.0
|
||||
|
||||
def test_negative_estimated_duration(self):
|
||||
t = Template(id="t1", user_id="u1", name="n", mode="one_take", estimated_duration=-1.0)
|
||||
assert t.estimated_duration == -1.0
|
||||
|
||||
def test_large_estimated_duration(self):
|
||||
t = Template(id="t1", user_id="u1", name="n", mode="one_take", estimated_duration=9999.9)
|
||||
assert t.estimated_duration == 9999.9
|
||||
|
||||
def test_empty_category(self):
|
||||
t = Template(id="t1", user_id="u1", name="n", mode="one_take", category="")
|
||||
assert t.category == ""
|
||||
|
||||
def test_custom_category(self):
|
||||
t = Template(id="t1", user_id="u1", name="n", mode="one_take", category="美食探店")
|
||||
assert t.category == "美食探店"
|
||||
|
||||
def test_empty_name(self):
|
||||
t = Template(id="t1", user_id="u1", name="", mode="one_take")
|
||||
assert t.name == ""
|
||||
|
||||
def test_unicode_name(self):
|
||||
t = Template(id="t1", user_id="u1", name="🎬 美食探店 · Vlog模板", mode="one_take")
|
||||
assert "🎬" in t.name
|
||||
assert "美食探店" in t.name
|
||||
|
||||
def test_special_characters_in_name(self):
|
||||
special = "模!@#$%板"
|
||||
t = Template(id="t1", user_id="u1", name=special, mode="one_take")
|
||||
assert t.name == special
|
||||
|
||||
def test_long_name(self):
|
||||
long_name = "模板" * 100
|
||||
t = Template(id="t1", user_id="u1", name=long_name, mode="one_take")
|
||||
assert t.name == long_name
|
||||
assert len(t.name) == 200
|
||||
|
||||
|
||||
class TestTemplateSegmentExtended:
|
||||
"""TemplateSegment 深度补充测试"""
|
||||
|
||||
def test_zero_duration(self):
|
||||
seg = TemplateSegment(id="s1", template_id="t1", segment_order=0, duration_min=0, duration_max=0)
|
||||
assert seg.duration_min == 0
|
||||
assert seg.duration_max == 0
|
||||
|
||||
def test_negative_duration_min(self):
|
||||
seg = TemplateSegment(id="s1", template_id="t1", segment_order=0, duration_min=-1, duration_max=5)
|
||||
assert seg.duration_min == -1
|
||||
|
||||
def test_negative_duration_max(self):
|
||||
seg = TemplateSegment(id="s1", template_id="t1", segment_order=0, duration_min=1, duration_max=-5)
|
||||
assert seg.duration_max == -5
|
||||
|
||||
def test_large_duration(self):
|
||||
seg = TemplateSegment(id="s1", template_id="t1", segment_order=0, duration_min=0, duration_max=9999.9)
|
||||
assert seg.duration_max == 9999.9
|
||||
|
||||
def test_negative_order(self):
|
||||
seg = TemplateSegment(id="s1", template_id="t1", segment_order=-5, duration_min=1, duration_max=3)
|
||||
assert seg.segment_order == -5
|
||||
|
||||
def test_large_order(self):
|
||||
seg = TemplateSegment(id="s1", template_id="t1", segment_order=999, duration_min=1, duration_max=3)
|
||||
assert seg.segment_order == 999
|
||||
|
||||
def test_material_type_none(self):
|
||||
seg = TemplateSegment(
|
||||
id="s1", template_id="t1", segment_order=0, duration_min=1, duration_max=3, material_type=None
|
||||
)
|
||||
assert seg.material_type is None
|
||||
|
||||
def test_material_type_empty_string(self):
|
||||
seg = TemplateSegment(
|
||||
id="s1", template_id="t1", segment_order=0, duration_min=1, duration_max=3, material_type=""
|
||||
)
|
||||
assert seg.material_type == ""
|
||||
|
||||
def test_material_type_unicode(self):
|
||||
seg = TemplateSegment(
|
||||
id="s1", template_id="t1", segment_order=0, duration_min=1, duration_max=3, material_type="风景"
|
||||
)
|
||||
assert seg.material_type == "风景"
|
||||
|
||||
Regular → Executable
+78
@@ -125,3 +125,81 @@ class TestEditTemplateVersionSlots:
|
||||
v = EditTemplateVersion.create(template_id="t1", version=1)
|
||||
with pytest.raises(AttributeError):
|
||||
v.nonexistent_field = "value" # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class TestEditTemplateVersionExtended:
|
||||
"""EditTemplateVersion 深度补充测试"""
|
||||
|
||||
def test_id_is_hex(self):
|
||||
"""id 是十六进制字符串"""
|
||||
v = EditTemplateVersion.create(template_id="t1", version=1)
|
||||
int(v.id, 16) # 不抛错就是合法 hex
|
||||
|
||||
def test_ids_are_unique(self):
|
||||
"""不同版本的 id 不同"""
|
||||
v1 = EditTemplateVersion.create(template_id="t1", version=1)
|
||||
v2 = EditTemplateVersion.create(template_id="t1", version=2)
|
||||
assert v1.id != v2.id
|
||||
|
||||
def test_zero_version(self):
|
||||
"""version=0 也能创建"""
|
||||
v = EditTemplateVersion.create(template_id="t1", version=0)
|
||||
assert v.version == 0
|
||||
|
||||
def test_negative_version(self):
|
||||
"""负数 version 也能创建(领域层不做业务校验)"""
|
||||
v = EditTemplateVersion.create(template_id="t1", version=-1)
|
||||
assert v.version == -1
|
||||
|
||||
def test_large_version(self):
|
||||
v = EditTemplateVersion.create(template_id="t1", version=9999)
|
||||
assert v.version == 9999
|
||||
|
||||
def test_empty_template_id(self):
|
||||
v = EditTemplateVersion.create(template_id="", version=1)
|
||||
assert v.template_id == ""
|
||||
|
||||
def test_empty_name(self):
|
||||
v = EditTemplateVersion.create(template_id="t1", version=1, name="")
|
||||
assert v.name == ""
|
||||
|
||||
def test_empty_change_note(self):
|
||||
v = EditTemplateVersion.create(template_id="t1", version=1, change_note="")
|
||||
assert v.change_note == ""
|
||||
|
||||
def test_empty_published_by(self):
|
||||
v = EditTemplateVersion.create(template_id="t1", version=1, published_by="")
|
||||
assert v.published_by == ""
|
||||
|
||||
def test_unicode_name(self):
|
||||
v = EditTemplateVersion.create(template_id="t1", version=1, name="🎨 V5 优化版")
|
||||
assert "🎨" in v.name
|
||||
assert "V5" in v.name
|
||||
|
||||
def test_long_name(self):
|
||||
long_name = "版本" * 50
|
||||
v = EditTemplateVersion.create(template_id="t1", version=1, name=long_name)
|
||||
assert v.name == long_name
|
||||
assert len(v.name) == 100
|
||||
|
||||
def test_config_complex_nested(self):
|
||||
config = {
|
||||
"layer1": {
|
||||
"layer2": {
|
||||
"layer3": [1, 2, 3],
|
||||
}
|
||||
}
|
||||
}
|
||||
v = EditTemplateVersion.create(template_id="t1", version=1, config=config)
|
||||
assert v.config["layer1"]["layer2"]["layer3"] == [1, 2, 3]
|
||||
|
||||
def test_clip_configs_empty_list(self):
|
||||
v = EditTemplateVersion.create(template_id="t1", version=1, clip_configs=[])
|
||||
assert v.clip_configs == []
|
||||
|
||||
def test_clip_configs_many(self):
|
||||
clips = [{"clip_id": i, "duration": float(i)} for i in range(50)]
|
||||
v = EditTemplateVersion.create(template_id="t1", version=1, clip_configs=clips)
|
||||
assert len(v.clip_configs) == 50
|
||||
assert v.clip_configs[0]["clip_id"] == 0
|
||||
assert v.clip_configs[49]["clip_id"] == 49
|
||||
|
||||
Reference in New Issue
Block a user