test: P3-1 第50波单元测试(generation_task/edit_plan/edit_template/edit_plan_clip,+86) #838
Executable
+78
@@ -0,0 +1,78 @@
|
||||
"""EditPlan 剪辑计划领域实体单测."""
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.domain.edit_plan import EditPlan, EditPlanStatus
|
||||
|
||||
|
||||
class TestEditPlanStatus:
|
||||
def test_values(self):
|
||||
assert EditPlanStatus.DRAFT.value == "draft"
|
||||
assert EditPlanStatus.EDITING.value == "editing"
|
||||
assert EditPlanStatus.RENDERING.value == "rendering"
|
||||
assert EditPlanStatus.COMPLETED.value == "completed"
|
||||
assert EditPlanStatus.FAILED.value == "failed"
|
||||
|
||||
def test_is_str(self):
|
||||
assert isinstance(EditPlanStatus.DRAFT, str)
|
||||
|
||||
|
||||
class TestEditPlanCreate:
|
||||
def test_create_normal(self):
|
||||
plan = EditPlan.create(template_id="tmpl1", name="我的剪辑计划")
|
||||
assert plan.id
|
||||
assert plan.template_id == "tmpl1"
|
||||
assert plan.name == "我的剪辑计划"
|
||||
assert plan.status == EditPlanStatus.DRAFT
|
||||
assert plan.total_duration == 0.0
|
||||
assert plan.config == {}
|
||||
|
||||
def test_create_strips_whitespace(self):
|
||||
plan = EditPlan.create(
|
||||
template_id=" tmpl1 ",
|
||||
name=" 我的计划 ",
|
||||
source_edit_plan_id=" src1 ",
|
||||
project_id=" proj1 ",
|
||||
created_by_user_id=" user1 ",
|
||||
)
|
||||
assert plan.template_id == "tmpl1"
|
||||
assert plan.name == "我的计划"
|
||||
assert plan.source_edit_plan_id == "src1"
|
||||
assert plan.project_id == "proj1"
|
||||
assert plan.created_by_user_id == "user1"
|
||||
|
||||
def test_create_empty_name_raises(self):
|
||||
with pytest.raises(ValueError, match="计划名称不能为空"):
|
||||
EditPlan.create(template_id="tmpl1", name="")
|
||||
|
||||
def test_create_whitespace_name_raises(self):
|
||||
with pytest.raises(ValueError, match="计划名称不能为空"):
|
||||
EditPlan.create(template_id="tmpl1", name=" ")
|
||||
|
||||
def test_create_empty_template_id_raises(self):
|
||||
with pytest.raises(ValueError, match="template_id 不能为空"):
|
||||
EditPlan.create(template_id="", name="计划")
|
||||
|
||||
def test_create_whitespace_template_id_raises(self):
|
||||
with pytest.raises(ValueError, match="template_id 不能为空"):
|
||||
EditPlan.create(template_id=" ", name="计划")
|
||||
|
||||
def test_create_with_config(self):
|
||||
config = {"resolution": "1080p", "fps": 30}
|
||||
plan = EditPlan.create(
|
||||
template_id="tmpl1",
|
||||
name="计划",
|
||||
config=config,
|
||||
total_duration=30.5,
|
||||
)
|
||||
assert plan.config == config
|
||||
assert plan.total_duration == 30.5
|
||||
|
||||
def test_create_none_config_defaults_to_empty(self):
|
||||
plan = EditPlan.create(template_id="tmpl1", name="计划", config=None)
|
||||
assert plan.config == {}
|
||||
|
||||
def test_create_unique_ids(self):
|
||||
p1 = EditPlan.create(template_id="t1", name="p1")
|
||||
p2 = EditPlan.create(template_id="t1", name="p2")
|
||||
assert p1.id != p2.id
|
||||
Executable
+181
@@ -0,0 +1,181 @@
|
||||
"""EditPlanClip 剪辑计划片段领域实体单测."""
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.domain.edit_plan_clip import EditPlanClip, EditPlanClipStatus
|
||||
|
||||
|
||||
class TestEditPlanClipStatus:
|
||||
def test_values(self):
|
||||
assert EditPlanClipStatus.PENDING.value == "pending"
|
||||
assert EditPlanClipStatus.READY.value == "ready"
|
||||
assert EditPlanClipStatus.RENDERED.value == "rendered"
|
||||
assert EditPlanClipStatus.FAILED.value == "failed"
|
||||
|
||||
|
||||
class TestEditPlanClipCreate:
|
||||
def test_create_normal(self):
|
||||
clip = EditPlanClip.create(
|
||||
plan_id="plan1",
|
||||
clip_type="video",
|
||||
order=1,
|
||||
start_time=0.0,
|
||||
duration=5.0,
|
||||
)
|
||||
assert clip.id
|
||||
assert clip.plan_id == "plan1"
|
||||
assert clip.clip_type == "video"
|
||||
assert clip.order == 1
|
||||
assert clip.status == EditPlanClipStatus.PENDING
|
||||
assert clip.start_time == 0.0
|
||||
assert clip.duration == 5.0
|
||||
assert clip.playback_speed == 1.0
|
||||
assert clip.config == {}
|
||||
|
||||
def test_create_empty_plan_id_raises(self):
|
||||
with pytest.raises(ValueError, match="plan_id 不能为空"):
|
||||
EditPlanClip.create(plan_id="", clip_type="video", order=1)
|
||||
|
||||
def test_create_empty_clip_type_raises(self):
|
||||
with pytest.raises(ValueError, match="clip_type 不能为空"):
|
||||
EditPlanClip.create(plan_id="p1", clip_type="", order=1)
|
||||
|
||||
def test_create_negative_start_time_raises(self):
|
||||
with pytest.raises(ValueError, match="start_time 不能为负数"):
|
||||
EditPlanClip.create(plan_id="p1", clip_type="v", order=1, start_time=-1.0)
|
||||
|
||||
def test_create_negative_duration_raises(self):
|
||||
with pytest.raises(ValueError, match="duration 不能为负数"):
|
||||
EditPlanClip.create(plan_id="p1", clip_type="v", order=1, duration=-1.0)
|
||||
|
||||
def test_create_strips_strings(self):
|
||||
clip = EditPlanClip.create(
|
||||
plan_id=" plan1 ",
|
||||
clip_type=" video ",
|
||||
order=1,
|
||||
template_clip_config_id=" cfg1 ",
|
||||
asset_id=" a1 ",
|
||||
text_content=" 你好 ",
|
||||
transition_effect=" fade ",
|
||||
)
|
||||
assert clip.plan_id == "plan1"
|
||||
assert clip.clip_type == "video"
|
||||
assert clip.template_clip_config_id == "cfg1"
|
||||
assert clip.asset_id == "a1"
|
||||
assert clip.text_content == "你好"
|
||||
assert clip.transition_effect == "fade"
|
||||
|
||||
def test_create_empty_transition_effect_defaults_to_cut(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, transition_effect="")
|
||||
assert clip.transition_effect == "cut"
|
||||
|
||||
def test_create_playback_speed_zero_defaults_to_1(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, playback_speed=0)
|
||||
assert clip.playback_speed == 1.0
|
||||
|
||||
def test_create_playback_speed_negative_defaults_to_1(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, playback_speed=-1.0)
|
||||
assert clip.playback_speed == 1.0
|
||||
|
||||
def test_create_playback_speed_below_min_clamped(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, playback_speed=0.1)
|
||||
assert clip.playback_speed == 0.25
|
||||
|
||||
def test_create_playback_speed_above_max_clamped(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, playback_speed=5.0)
|
||||
assert clip.playback_speed == 4.0
|
||||
|
||||
def test_create_playback_speed_within_range(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, playback_speed=1.5)
|
||||
assert clip.playback_speed == 1.5
|
||||
|
||||
def test_create_transition_duration_negative_clamped(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, transition_duration=-0.5)
|
||||
assert clip.transition_duration == 0.0
|
||||
|
||||
def test_create_none_config_defaults_to_empty(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, config=None)
|
||||
assert clip.config == {}
|
||||
|
||||
def test_create_unique_ids(self):
|
||||
c1 = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
c2 = EditPlanClip.create(plan_id="p1", clip_type="v", order=2)
|
||||
assert c1.id != c2.id
|
||||
|
||||
|
||||
class TestEditPlanClipStateTransitions:
|
||||
def test_pending_to_ready(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
clip.mark_ready()
|
||||
assert clip.status == EditPlanClipStatus.READY
|
||||
|
||||
def test_ready_to_rendered(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
clip.mark_ready()
|
||||
clip.mark_rendered()
|
||||
assert clip.status == EditPlanClipStatus.RENDERED
|
||||
|
||||
def test_ready_to_failed(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
clip.mark_ready()
|
||||
clip.mark_failed()
|
||||
assert clip.status == EditPlanClipStatus.FAILED
|
||||
|
||||
def test_ready_mark_ready_raises(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
clip.mark_ready()
|
||||
with pytest.raises(ValueError, match="只有 pending 状态的片段可以标记就绪"):
|
||||
clip.mark_ready()
|
||||
|
||||
def test_pending_mark_rendered_raises(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
with pytest.raises(ValueError, match="只有 ready 状态的片段可以标记已渲染"):
|
||||
clip.mark_rendered()
|
||||
|
||||
def test_pending_mark_failed_raises(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
with pytest.raises(ValueError, match="只有 ready 状态的片段可以标记失败"):
|
||||
clip.mark_failed()
|
||||
|
||||
|
||||
class TestEditPlanClipProperties:
|
||||
def test_end_time(self):
|
||||
clip = EditPlanClip.create(
|
||||
plan_id="p1", clip_type="v", order=1, start_time=10.0, duration=5.0
|
||||
)
|
||||
assert clip.end_time == 15.0
|
||||
|
||||
def test_end_time_zero_duration(self):
|
||||
clip = EditPlanClip.create(
|
||||
plan_id="p1", clip_type="v", order=1, start_time=5.0, duration=0.0
|
||||
)
|
||||
assert clip.end_time == 5.0
|
||||
|
||||
def test_has_asset_true(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, asset_id="a1")
|
||||
assert clip.has_asset is True
|
||||
|
||||
def test_has_asset_false(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
assert clip.has_asset is False
|
||||
|
||||
def test_has_asset_empty_string(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, asset_id="")
|
||||
assert clip.has_asset is False
|
||||
|
||||
|
||||
class TestEditPlanClipAssignAsset:
|
||||
def test_assign_asset(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
clip.assign_asset("asset1")
|
||||
assert clip.asset_id == "asset1"
|
||||
|
||||
def test_assign_asset_strips(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
clip.assign_asset(" asset1 ")
|
||||
assert clip.asset_id == "asset1"
|
||||
|
||||
def test_assign_asset_empty_raises(self):
|
||||
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1)
|
||||
with pytest.raises(ValueError, match="asset_id 不能为空"):
|
||||
clip.assign_asset("")
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
"""EditTemplate 剪辑模板领域实体单测."""
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.domain.edit_template import EditTemplate, EditTemplateStatus
|
||||
from packages.domain.editing_mode import EditingMode
|
||||
|
||||
|
||||
class TestEditTemplateStatus:
|
||||
def test_values(self):
|
||||
assert EditTemplateStatus.ACTIVE.value == "active"
|
||||
assert EditTemplateStatus.INACTIVE.value == "inactive"
|
||||
|
||||
|
||||
class TestEditTemplateCreate:
|
||||
def test_create_default(self):
|
||||
tpl = EditTemplate.create(name="测试模板")
|
||||
assert tpl.id
|
||||
assert tpl.name == "测试模板"
|
||||
assert tpl.editing_mode == EditingMode.ONE_TAKE.value
|
||||
assert tpl.status == EditTemplateStatus.ACTIVE
|
||||
assert tpl.version == 1
|
||||
assert tpl.config == {}
|
||||
assert tpl.description == ""
|
||||
assert tpl.sort_weight == 0
|
||||
|
||||
def test_create_strips_name(self):
|
||||
tpl = EditTemplate.create(name=" 我的模板 ")
|
||||
assert tpl.name == "我的模板"
|
||||
|
||||
def test_create_empty_name_raises(self):
|
||||
with pytest.raises(ValueError, match="模板名称不能为空"):
|
||||
EditTemplate.create(name="")
|
||||
|
||||
def test_create_whitespace_name_raises(self):
|
||||
with pytest.raises(ValueError, match="模板名称不能为空"):
|
||||
EditTemplate.create(name=" ")
|
||||
|
||||
def test_create_valid_editing_modes(self):
|
||||
for mode in EditingMode:
|
||||
tpl = EditTemplate.create(name=f"模板_{mode.value}", editing_mode=mode.value)
|
||||
assert tpl.editing_mode == mode.value
|
||||
|
||||
def test_create_invalid_editing_mode_raises(self):
|
||||
with pytest.raises(ValueError, match="无效的 editing_mode"):
|
||||
EditTemplate.create(name="模板", editing_mode="invalid_mode")
|
||||
|
||||
def test_create_empty_editing_mode_defaults_to_one_take(self):
|
||||
tpl = EditTemplate.create(name="模板", editing_mode="")
|
||||
assert tpl.editing_mode == EditingMode.ONE_TAKE.value
|
||||
|
||||
def test_create_with_config(self):
|
||||
config = {"key": "value"}
|
||||
tpl = EditTemplate.create(name="模板", config=config)
|
||||
assert tpl.config == config
|
||||
|
||||
def test_create_none_config_defaults_to_empty(self):
|
||||
tpl = EditTemplate.create(name="模板", config=None)
|
||||
assert tpl.config == {}
|
||||
|
||||
def test_create_with_custom_status(self):
|
||||
tpl = EditTemplate.create(name="模板", status=EditTemplateStatus.INACTIVE)
|
||||
assert tpl.status == EditTemplateStatus.INACTIVE
|
||||
|
||||
def test_create_unique_ids(self):
|
||||
t1 = EditTemplate.create(name="t1")
|
||||
t2 = EditTemplate.create(name="t2")
|
||||
assert t1.id != t2.id
|
||||
Executable
+300
@@ -0,0 +1,300 @@
|
||||
"""GenerationTask 生成任务领域模型单测."""
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.domain.generation_task import (
|
||||
TERMINAL_STATUSES,
|
||||
GenerationTask,
|
||||
GenerationTaskStatus,
|
||||
)
|
||||
|
||||
|
||||
class TestGenerationTaskCreate:
|
||||
def test_create_with_template_id(self):
|
||||
task = GenerationTask.create(
|
||||
project_id="",
|
||||
asset_library_id="lib1",
|
||||
template_id="tmpl1",
|
||||
)
|
||||
assert task.id
|
||||
assert task.template_id == "tmpl1"
|
||||
assert task.asset_library_id == "lib1"
|
||||
assert task.status == GenerationTaskStatus.PENDING
|
||||
assert task.progress == 0.0
|
||||
assert task.retry_count == 0
|
||||
assert task.auto_retry_enabled is False
|
||||
|
||||
def test_create_with_project_id(self):
|
||||
task = GenerationTask.create(
|
||||
project_id="proj1",
|
||||
asset_library_id="lib1",
|
||||
)
|
||||
assert task.project_id == "proj1"
|
||||
|
||||
def test_create_both_empty_raises(self):
|
||||
with pytest.raises(ValueError, match="project_id 或 template_id 至少需要提供一个"):
|
||||
GenerationTask.create(
|
||||
project_id="",
|
||||
asset_library_id="lib1",
|
||||
template_id="",
|
||||
)
|
||||
|
||||
def test_create_asset_library_and_assets_both_empty_raises(self):
|
||||
with pytest.raises(ValueError, match="asset_library_id 或 asset_ids/title_ids/voice_ids 至少需要提供一个"):
|
||||
GenerationTask.create(
|
||||
project_id="proj1",
|
||||
asset_library_id="",
|
||||
asset_ids=None,
|
||||
title_ids=None,
|
||||
voice_ids=None,
|
||||
)
|
||||
|
||||
def test_create_with_asset_ids(self):
|
||||
task = GenerationTask.create(
|
||||
project_id="proj1",
|
||||
asset_library_id="",
|
||||
asset_ids=["a1", "a2"],
|
||||
)
|
||||
assert task.asset_ids == ["a1", "a2"]
|
||||
|
||||
def test_create_strips_whitespace(self):
|
||||
task = GenerationTask.create(
|
||||
project_id=" proj1 ",
|
||||
asset_library_id=" lib1 ",
|
||||
template_id=" tmpl1 ",
|
||||
video_title=" 测试视频 ",
|
||||
resolution=" 1080p ",
|
||||
created_by_user_id=" user1 ",
|
||||
source_edit_plan_id=" plan1 ",
|
||||
)
|
||||
assert task.project_id == "proj1"
|
||||
assert task.asset_library_id == "lib1"
|
||||
assert task.template_id == "tmpl1"
|
||||
assert task.video_title == "测试视频"
|
||||
assert task.resolution == "1080p"
|
||||
assert task.created_by_user_id == "user1"
|
||||
assert task.source_edit_plan_id == "plan1"
|
||||
|
||||
def test_create_default_values(self):
|
||||
task = GenerationTask.create(
|
||||
project_id="proj1",
|
||||
asset_library_id="lib1",
|
||||
)
|
||||
assert task.title_ids == []
|
||||
assert task.voice_ids == []
|
||||
assert task.result_count == 0
|
||||
assert task.error_message == ""
|
||||
assert task.error_info == {}
|
||||
assert task.bgm_config == {}
|
||||
assert task.logs == "[]"
|
||||
|
||||
def test_create_unique_ids(self):
|
||||
t1 = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
t2 = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
assert t1.id != t2.id
|
||||
|
||||
|
||||
class TestGenerationTaskStatusQueries:
|
||||
def test_is_terminal_completed(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.mark_processing()
|
||||
task.mark_completed()
|
||||
assert task.is_terminal is True
|
||||
assert task.is_completed is True
|
||||
assert task.is_failed is False
|
||||
assert task.is_running is False
|
||||
|
||||
def test_is_terminal_failed(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.mark_processing()
|
||||
task.mark_failed("error")
|
||||
assert task.is_terminal is True
|
||||
assert task.is_completed is False
|
||||
assert task.is_failed is True
|
||||
|
||||
def test_is_terminal_cancelled(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.transition_to(GenerationTaskStatus.CANCELLED)
|
||||
assert task.is_terminal is True
|
||||
|
||||
def test_is_running(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
assert task.is_running is False
|
||||
task.mark_processing()
|
||||
assert task.is_running is True
|
||||
|
||||
def test_terminal_statuses_set(self):
|
||||
assert GenerationTaskStatus.COMPLETED in TERMINAL_STATUSES
|
||||
assert GenerationTaskStatus.FAILED in TERMINAL_STATUSES
|
||||
assert GenerationTaskStatus.CANCELLED in TERMINAL_STATUSES
|
||||
assert GenerationTaskStatus.PENDING not in TERMINAL_STATUSES
|
||||
assert GenerationTaskStatus.RUNNING not in TERMINAL_STATUSES
|
||||
|
||||
|
||||
class TestGenerationTaskStateTransitions:
|
||||
def test_pending_to_running(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.mark_processing()
|
||||
assert task.status == GenerationTaskStatus.RUNNING
|
||||
assert task.started_at is not None
|
||||
assert task.error_message == ""
|
||||
|
||||
def test_running_to_completed(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.mark_processing()
|
||||
task.mark_completed(result_count=3)
|
||||
assert task.status == GenerationTaskStatus.COMPLETED
|
||||
assert task.completed_at is not None
|
||||
assert task.progress == 100.0
|
||||
assert task.result_count == 3
|
||||
assert task.error_message == ""
|
||||
|
||||
def test_running_to_failed_with_error_info(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.mark_processing()
|
||||
task.mark_failed("渲染失败", error_info={"stage": "render"})
|
||||
assert task.status == GenerationTaskStatus.FAILED
|
||||
assert task.completed_at is not None
|
||||
assert task.error_message == "渲染失败"
|
||||
assert task.error_info["stage"] == "render"
|
||||
|
||||
def test_running_to_failed_without_error_info(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.mark_processing()
|
||||
task.mark_failed("未知错误")
|
||||
assert task.error_info is not None
|
||||
assert task.error_info["message"] == "未知错误"
|
||||
assert "error_type" in task.error_info
|
||||
assert "failed_at" in task.error_info
|
||||
|
||||
def test_failed_to_pending_retry(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.mark_processing()
|
||||
task.mark_failed("error")
|
||||
assert task.retry_count == 0
|
||||
|
||||
task.mark_pending_from_failed()
|
||||
assert task.status == GenerationTaskStatus.PENDING
|
||||
assert task.retry_count == 1
|
||||
assert task.error_message == ""
|
||||
assert task.error_info == {}
|
||||
assert task.started_at is None
|
||||
assert task.completed_at is None
|
||||
assert task.progress == 0.0
|
||||
assert task.result_count == 0
|
||||
|
||||
def test_mark_pending_from_failed_wrong_status_raises(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
with pytest.raises(ValueError, match="只有 failed 状态的任务可以重置为 pending"):
|
||||
task.mark_pending_from_failed()
|
||||
|
||||
def test_invalid_transition_raises(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
# pending 不能直接到 completed
|
||||
with pytest.raises(ValueError, match="非法状态转换"):
|
||||
task.transition_to(GenerationTaskStatus.COMPLETED)
|
||||
|
||||
def test_completed_cannot_transition(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.mark_processing()
|
||||
task.mark_completed()
|
||||
with pytest.raises(ValueError, match="非法状态转换"):
|
||||
task.mark_failed("test")
|
||||
|
||||
def test_transition_to_with_string(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.transition_to("running")
|
||||
assert task.status == GenerationTaskStatus.RUNNING
|
||||
|
||||
def test_transition_to_invalid_string_raises(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
with pytest.raises(ValueError, match="无效状态"):
|
||||
task.transition_to("invalid_status")
|
||||
|
||||
|
||||
class TestGenerationTaskLogs:
|
||||
def test_append_log_single(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.append_log("初始化", "任务创建成功")
|
||||
logs = task.get_logs()
|
||||
assert len(logs) == 1
|
||||
assert logs[0]["stage"] == "初始化"
|
||||
assert logs[0]["message"] == "任务创建成功"
|
||||
assert logs[0]["level"] == "INFO"
|
||||
assert "ts" in logs[0]
|
||||
|
||||
def test_append_log_multiple(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
for i in range(5):
|
||||
task.append_log(f"stage{i}", f"msg{i}", level="INFO")
|
||||
logs = task.get_logs()
|
||||
assert len(logs) == 5
|
||||
assert logs[0]["stage"] == "stage0"
|
||||
assert logs[4]["stage"] == "stage4"
|
||||
|
||||
def test_append_log_with_extra_fields(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.append_log("下载", "下载完成", asset_id="a1", duration=10.5)
|
||||
logs = task.get_logs()
|
||||
assert logs[0]["asset_id"] == "a1"
|
||||
assert logs[0]["duration"] == 10.5
|
||||
|
||||
def test_append_log_error_level(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.append_log("渲染", "渲染失败", level="ERROR")
|
||||
logs = task.get_logs()
|
||||
assert logs[0]["level"] == "ERROR"
|
||||
|
||||
def test_logs_max_limit(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
# _MAX_LOGS = 200
|
||||
for i in range(250):
|
||||
task.append_log("test", f"msg{i}")
|
||||
logs = task.get_logs()
|
||||
assert len(logs) == 200
|
||||
# 保留最新的200条
|
||||
assert logs[0]["message"] == "msg50"
|
||||
assert logs[-1]["message"] == "msg249"
|
||||
|
||||
def test_get_logs_empty(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
assert task.get_logs() == []
|
||||
|
||||
def test_get_logs_corrupted_json(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.logs = "not json"
|
||||
assert task.get_logs() == []
|
||||
|
||||
def test_mark_failed_without_error_info(self):
|
||||
task = GenerationTask.create(project_id="p1", asset_library_id="l1")
|
||||
task.mark_processing()
|
||||
task.mark_failed("error msg")
|
||||
assert task.error_info is not None
|
||||
assert task.error_info["message"] == "error msg"
|
||||
assert "error_type" in task.error_info
|
||||
assert "failed_at" in task.error_info
|
||||
|
||||
|
||||
class TestGenerationTaskCreateWithStrategy:
|
||||
def test_create_with_strategy_and_voice(self):
|
||||
task = GenerationTask.create(
|
||||
project_id="p1",
|
||||
asset_library_id="l1",
|
||||
strategy_id="s1",
|
||||
voice_library_id="v1",
|
||||
auto_retry_enabled=True,
|
||||
auto_retry_max=3,
|
||||
)
|
||||
assert task.strategy_id == "s1"
|
||||
assert task.voice_library_id == "v1"
|
||||
assert task.auto_retry_enabled is True
|
||||
assert task.auto_retry_max == 3
|
||||
|
||||
def test_create_with_bgm_config(self):
|
||||
bgm = {"volume": 0.5, "track": "bgm1"}
|
||||
task = GenerationTask.create(
|
||||
project_id="p1",
|
||||
asset_library_id="l1",
|
||||
bgm_config=bgm,
|
||||
)
|
||||
assert task.bgm_config == bgm
|
||||
Reference in New Issue
Block a user