6930d4543f
CI/CD Pipeline / Check if frontend-only change (push) Blocked by required conditions
CI/CD Pipeline / Validate - Code Quality (push) Blocked by required conditions
CI/CD Pipeline / Validate - Type Check (mypy) (push) Blocked by required conditions
CI/CD Pipeline / Validate - Migration (alembic) (push) Blocked by required conditions
CI/CD Pipeline / Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (push) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (push) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (push) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging API Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (push) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (push) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (push) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Deploy Production (push) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (push) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (push) Blocked by required conditions
CI/CD Pipeline / CI Gate (push) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
282 lines
10 KiB
Python
Executable File
282 lines
10 KiB
Python
Executable File
"""edit_template 剪辑模板实体单测."""
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
import pytest
|
|
from domain.edit_template import EditTemplate, EditTemplateStatus
|
|
from domain.editing_mode import EditingMode
|
|
|
|
# ── EditTemplateStatus 枚举 ──────────────────────────────────────────────────
|
|
|
|
|
|
class TestEditTemplateStatus:
|
|
"""EditTemplateStatus 枚举"""
|
|
|
|
def test_enum_values(self):
|
|
assert EditTemplateStatus.ACTIVE.value == "active"
|
|
assert EditTemplateStatus.INACTIVE.value == "inactive"
|
|
|
|
def test_is_str_enum(self):
|
|
assert isinstance(EditTemplateStatus.ACTIVE, str)
|
|
assert EditTemplateStatus.ACTIVE == "active"
|
|
|
|
def test_from_string(self):
|
|
assert EditTemplateStatus("active") == EditTemplateStatus.ACTIVE
|
|
assert EditTemplateStatus("inactive") == EditTemplateStatus.INACTIVE
|
|
|
|
def test_invalid_raises(self):
|
|
with pytest.raises(ValueError):
|
|
EditTemplateStatus("deleted")
|
|
|
|
|
|
# ── EditTemplate.create 工厂方法 ────────────────────────────────────────────
|
|
|
|
|
|
class TestEditTemplateCreate:
|
|
"""EditTemplate.create 工厂方法"""
|
|
|
|
def test_minimal_create(self):
|
|
t = EditTemplate.create("测试模板")
|
|
assert t.id is not None
|
|
assert len(t.id) == 32 # uuid4 hex
|
|
assert t.name == "测试模板"
|
|
assert t.description == ""
|
|
assert t.template_type == "default"
|
|
assert t.editing_mode == "one_take"
|
|
assert t.config == {}
|
|
assert t.preview_url == ""
|
|
assert t.sort_weight == 0
|
|
assert t.status == EditTemplateStatus.ACTIVE
|
|
assert t.version == 1
|
|
|
|
def test_unique_ids(self):
|
|
t1 = EditTemplate.create("模板A")
|
|
t2 = EditTemplate.create("模板B")
|
|
assert t1.id != t2.id
|
|
|
|
def test_custom_fields(self):
|
|
t = EditTemplate.create(
|
|
"自定义模板",
|
|
description="这是一个自定义模板",
|
|
template_type="story",
|
|
editing_mode="one_take",
|
|
config={"key": "value"},
|
|
preview_url="https://example.com/preview.mp4",
|
|
sort_weight=100,
|
|
status=EditTemplateStatus.INACTIVE,
|
|
version=2,
|
|
)
|
|
assert t.name == "自定义模板"
|
|
assert t.description == "这是一个自定义模板"
|
|
assert t.template_type == "story"
|
|
assert t.editing_mode == "one_take"
|
|
assert t.config == {"key": "value"}
|
|
assert t.preview_url == "https://example.com/preview.mp4"
|
|
assert t.sort_weight == 100
|
|
assert t.status == EditTemplateStatus.INACTIVE
|
|
assert t.version == 2
|
|
|
|
def test_name_stripped(self):
|
|
t = EditTemplate.create(" 带空格的模板 ")
|
|
assert t.name == "带空格的模板"
|
|
|
|
def test_empty_name_raises(self):
|
|
with pytest.raises(ValueError, match="名称"):
|
|
EditTemplate.create("")
|
|
|
|
def test_whitespace_only_name_raises(self):
|
|
with pytest.raises(ValueError):
|
|
EditTemplate.create(" ")
|
|
|
|
def test_invalid_editing_mode_raises(self):
|
|
with pytest.raises(ValueError, match="editing_mode"):
|
|
EditTemplate.create("测试", editing_mode="invalid_mode")
|
|
|
|
def test_empty_editing_mode_falls_back_to_default(self):
|
|
t = EditTemplate.create("测试", editing_mode="")
|
|
assert t.editing_mode == "one_take"
|
|
|
|
def test_whitespace_editing_mode_falls_back(self):
|
|
t = EditTemplate.create("测试", editing_mode=" ")
|
|
assert t.editing_mode == "one_take"
|
|
|
|
def test_editing_mode_stripped(self):
|
|
t = EditTemplate.create("测试", editing_mode=" one_take ")
|
|
assert t.editing_mode == "one_take"
|
|
|
|
def test_description_stripped(self):
|
|
t = EditTemplate.create("测试", description=" 描述 ")
|
|
assert t.description == "描述"
|
|
|
|
def test_template_type_stripped(self):
|
|
t = EditTemplate.create("测试", template_type=" vlog ")
|
|
assert t.template_type == "vlog"
|
|
|
|
def test_empty_template_type_falls_back(self):
|
|
t = EditTemplate.create("测试", template_type="")
|
|
assert t.template_type == "default"
|
|
|
|
def test_none_config_becomes_empty_dict(self):
|
|
t = EditTemplate.create("测试", config=None)
|
|
assert t.config == {}
|
|
assert isinstance(t.config, dict)
|
|
|
|
def test_preview_url_stripped(self):
|
|
t = EditTemplate.create("测试", preview_url=" https://x.com/a.mp4 ")
|
|
assert t.preview_url == "https://x.com/a.mp4"
|
|
|
|
def test_timestamps_are_utc(self):
|
|
t = EditTemplate.create("测试")
|
|
assert t.created_at.tzinfo is not None
|
|
assert t.updated_at.tzinfo is not None
|
|
|
|
def test_created_at_equals_updated_at_on_create(self):
|
|
t = EditTemplate.create("测试")
|
|
# 创建时两个时间应该非常接近
|
|
diff = abs((t.updated_at - t.created_at).total_seconds())
|
|
assert diff < 1.0
|
|
|
|
|
|
# ── 状态操作 ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestEditTemplateStatusOperations:
|
|
"""EditTemplate 状态操作"""
|
|
|
|
def test_activate_sets_active(self):
|
|
t = EditTemplate.create("测试", status=EditTemplateStatus.INACTIVE)
|
|
t.activate()
|
|
assert t.status == EditTemplateStatus.ACTIVE
|
|
assert t.is_active is True
|
|
|
|
def test_deactivate_sets_inactive(self):
|
|
t = EditTemplate.create("测试")
|
|
t.deactivate()
|
|
assert t.status == EditTemplateStatus.INACTIVE
|
|
assert t.is_active is False
|
|
|
|
def test_is_active_true(self):
|
|
t = EditTemplate.create("测试")
|
|
assert t.is_active is True
|
|
|
|
def test_is_active_false(self):
|
|
t = EditTemplate.create("测试", status=EditTemplateStatus.INACTIVE)
|
|
assert t.is_active is False
|
|
|
|
def test_activate_updates_updated_at(self):
|
|
t = EditTemplate.create("测试", status=EditTemplateStatus.INACTIVE)
|
|
old_updated = t.updated_at
|
|
t.activate()
|
|
assert t.updated_at >= old_updated
|
|
|
|
def test_deactivate_updates_updated_at(self):
|
|
t = EditTemplate.create("测试")
|
|
old_updated = t.updated_at
|
|
t.deactivate()
|
|
assert t.updated_at >= old_updated
|
|
|
|
|
|
# ── 版本操作 ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestEditTemplateVersion:
|
|
"""EditTemplate 版本操作"""
|
|
|
|
def test_bump_version_increments(self):
|
|
t = EditTemplate.create("测试")
|
|
assert t.version == 1
|
|
t.bump_version()
|
|
assert t.version == 2
|
|
|
|
def test_bump_version_multiple(self):
|
|
t = EditTemplate.create("测试", version=5)
|
|
t.bump_version()
|
|
t.bump_version()
|
|
t.bump_version()
|
|
assert t.version == 8
|
|
|
|
def test_bump_version_updates_updated_at(self):
|
|
t = EditTemplate.create("测试")
|
|
old_updated = t.updated_at
|
|
t.bump_version()
|
|
assert t.updated_at >= old_updated
|
|
|
|
|
|
# ── dataclass 基础特性 ───────────────────────────────────────────────────────
|
|
|
|
|
|
class TestEditTemplateBasics:
|
|
"""EditTemplate 基础特性"""
|
|
|
|
def test_slots_no_extra_attrs(self):
|
|
t = EditTemplate.create("测试")
|
|
with pytest.raises(AttributeError):
|
|
t.nonexistent_field = "value"
|
|
|
|
def test_direct_construction_minimal(self):
|
|
# 最小构造:仅必填字段 + 状态,其余走默认值
|
|
t = EditTemplate(
|
|
id="custom_id",
|
|
name="直接构造",
|
|
status=EditTemplateStatus.ACTIVE,
|
|
)
|
|
assert t.id == "custom_id"
|
|
assert t.name == "直接构造"
|
|
assert t.status == EditTemplateStatus.ACTIVE
|
|
# 默认值检查
|
|
assert t.description == ""
|
|
assert t.config == {}
|
|
assert t.version == 1
|
|
assert t.editing_mode == EditingMode.ONE_TAKE.value
|
|
assert isinstance(t.created_at, datetime)
|
|
assert isinstance(t.updated_at, datetime)
|
|
|
|
def test_direct_construction_full(self):
|
|
# 完整构造:所有字段都传
|
|
now = datetime(2025, 1, 1, tzinfo=timezone.utc)
|
|
t = EditTemplate(
|
|
id="full_id",
|
|
name="完整构造",
|
|
description="测试描述",
|
|
template_type="custom",
|
|
editing_mode=EditingMode.PIP.value,
|
|
config={"key": "value"},
|
|
preview_url="https://example.com/preview.jpg",
|
|
sort_weight=100,
|
|
status=EditTemplateStatus.INACTIVE,
|
|
version=3,
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
assert t.id == "full_id"
|
|
assert t.name == "完整构造"
|
|
assert t.description == "测试描述"
|
|
assert t.template_type == "custom"
|
|
assert t.editing_mode == EditingMode.PIP.value
|
|
assert t.config == {"key": "value"}
|
|
assert t.preview_url == "https://example.com/preview.jpg"
|
|
assert t.sort_weight == 100
|
|
assert t.status == EditTemplateStatus.INACTIVE
|
|
assert t.version == 3
|
|
assert t.created_at == now
|
|
assert t.updated_at == now
|
|
|
|
def test_config_is_independent(self):
|
|
# 不同实例的 config 应该是独立的 dict
|
|
t1 = EditTemplate.create("模板1")
|
|
t2 = EditTemplate.create("模板2")
|
|
t1.config["key"] = "value"
|
|
assert "key" not in t2.config
|
|
|
|
def test_equality(self):
|
|
# 两个不同实例即使内容相同也不等(id不同)
|
|
t1 = EditTemplate.create("同名模板")
|
|
t2 = EditTemplate.create("同名模板")
|
|
assert t1 != t2
|
|
|
|
def test_same_id_equal(self):
|
|
now = datetime.now(timezone.utc)
|
|
t1 = EditTemplate(id="same", name="同名", created_at=now, updated_at=now)
|
|
t2 = EditTemplate(id="same", name="同名", created_at=now, updated_at=now)
|
|
assert t1 == t2
|