fix: 状态枚举添加_missing_兼容历史脏数据,修复Staging模板生成接口500 #809 #835
@@ -28,6 +28,28 @@ class EditPlanStatus(StrEnum):
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value: object) -> "EditPlanStatus":
|
||||
"""兼容历史脏数据,避免枚举转换失败导致500。
|
||||
|
||||
- success/done/finished/complete → COMPLETED
|
||||
- fail/error/err → FAILED
|
||||
- render/rendering → RENDERING
|
||||
- edit/editing → EDITING
|
||||
- 其他未知值 → DRAFT(兜底,不阻塞业务)
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
normalized = value.strip().lower()
|
||||
if normalized in ("done", "success", "finished", "complete", "completed"):
|
||||
return cls.COMPLETED
|
||||
if normalized in ("fail", "failed", "error", "err"):
|
||||
return cls.FAILED
|
||||
if normalized in ("render", "rendering", "generating", "generating_video"):
|
||||
return cls.RENDERING
|
||||
if normalized in ("edit", "editing", "working"):
|
||||
return cls.EDITING
|
||||
return cls.DRAFT
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class EditPlan:
|
||||
|
||||
@@ -31,6 +31,25 @@ class EditPlanClipStatus(StrEnum):
|
||||
RENDERED = "rendered" # 已渲染
|
||||
FAILED = "failed" # 渲染失败
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value: object) -> "EditPlanClipStatus":
|
||||
"""兼容历史脏数据,避免枚举转换失败导致500。
|
||||
|
||||
- success/done/finished/complete/rendered → RENDERED
|
||||
- fail/error/err → FAILED
|
||||
- ready/available → READY
|
||||
- 其他未知值 → PENDING(兜底,不阻塞业务)
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
normalized = value.strip().lower()
|
||||
if normalized in ("done", "success", "finished", "complete", "rendered", "render"):
|
||||
return cls.RENDERED
|
||||
if normalized in ("fail", "failed", "error", "err"):
|
||||
return cls.FAILED
|
||||
if normalized in ("ready", "available", "prepared"):
|
||||
return cls.READY
|
||||
return cls.PENDING
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class EditPlanClip:
|
||||
|
||||
@@ -43,6 +43,28 @@ class GenerationTaskStatus(StrEnum):
|
||||
CANCELLED = "cancelled"
|
||||
"""已取消(用户取消或系统取消)"""
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value: object) -> "GenerationTaskStatus":
|
||||
"""兼容历史脏数据,避免枚举转换失败导致500。
|
||||
|
||||
- success/done/finished/complete → COMPLETED
|
||||
- fail/error/err → FAILED
|
||||
- process/processing/run/running → RUNNING
|
||||
- cancel/canceled → CANCELLED
|
||||
- 其他未知值 → PENDING(兜底,不阻塞业务)
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
normalized = value.strip().lower()
|
||||
if normalized in ("done", "success", "finished", "complete", "completed"):
|
||||
return cls.COMPLETED
|
||||
if normalized in ("fail", "failed", "error", "err"):
|
||||
return cls.FAILED
|
||||
if normalized in ("process", "processing", "run", "running", "in_progress"):
|
||||
return cls.RUNNING
|
||||
if normalized in ("cancel", "cancelled", "canceled"):
|
||||
return cls.CANCELLED
|
||||
return cls.PENDING
|
||||
|
||||
|
||||
# 终态集合
|
||||
TERMINAL_STATUSES = frozenset(
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
"""GenerationTaskStatus 枚举兼容性测试。
|
||||
|
||||
验证历史脏数据(如 'success'/'done')不会导致枚举转换失败。
|
||||
关联 Issue: #809 [Staging] E2E测试失败 - 模板生成接口返回500
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.domain.generation_task import GenerationTaskStatus
|
||||
|
||||
|
||||
class TestGenerationTaskStatusNormalValues:
|
||||
"""正常值应该正确映射。"""
|
||||
|
||||
def test_pending(self):
|
||||
assert GenerationTaskStatus("pending") == GenerationTaskStatus.PENDING
|
||||
|
||||
def test_running(self):
|
||||
assert GenerationTaskStatus("running") == GenerationTaskStatus.RUNNING
|
||||
|
||||
def test_completed(self):
|
||||
assert GenerationTaskStatus("completed") == GenerationTaskStatus.COMPLETED
|
||||
|
||||
def test_failed(self):
|
||||
assert GenerationTaskStatus("failed") == GenerationTaskStatus.FAILED
|
||||
|
||||
def test_cancelled(self):
|
||||
assert GenerationTaskStatus("cancelled") == GenerationTaskStatus.CANCELLED
|
||||
|
||||
|
||||
class TestGenerationTaskStatusHistoricalValues:
|
||||
"""历史脏数据应该正确映射到对应状态,不抛异常。"""
|
||||
|
||||
@pytest.mark.parametrize("value", ["done", "success", "finished", "complete", "completed"])
|
||||
def test_completed_like_values_map_to_completed(self, value):
|
||||
assert GenerationTaskStatus(value) == GenerationTaskStatus.COMPLETED
|
||||
|
||||
@pytest.mark.parametrize("value", ["fail", "failed", "error", "err"])
|
||||
def test_failed_like_values_map_to_failed(self, value):
|
||||
assert GenerationTaskStatus(value) == GenerationTaskStatus.FAILED
|
||||
|
||||
@pytest.mark.parametrize("value", ["process", "processing", "run", "running", "in_progress"])
|
||||
def test_running_like_values_map_to_running(self, value):
|
||||
assert GenerationTaskStatus(value) == GenerationTaskStatus.RUNNING
|
||||
|
||||
@pytest.mark.parametrize("value", ["cancel", "cancelled", "canceled"])
|
||||
def test_cancelled_like_values_map_to_cancelled(self, value):
|
||||
assert GenerationTaskStatus(value) == GenerationTaskStatus.CANCELLED
|
||||
|
||||
@pytest.mark.parametrize("value", [" Done ", "SUCCESS", " failed "])
|
||||
def test_whitespace_and_case_insensitive(self, value):
|
||||
"""带空格和大小写不影响匹配。"""
|
||||
# 只要能找到对应状态且不抛异常即可
|
||||
result = GenerationTaskStatus(value)
|
||||
assert result in (
|
||||
GenerationTaskStatus.COMPLETED,
|
||||
GenerationTaskStatus.FAILED,
|
||||
)
|
||||
|
||||
|
||||
class TestGenerationTaskStatusFallback:
|
||||
"""完全未知的值兜底为 PENDING,不抛500。"""
|
||||
|
||||
@pytest.mark.parametrize("value", ["unknown", "foo_bar", "deleted", ""])
|
||||
def test_unknown_value_falls_back_to_pending(self, value):
|
||||
assert GenerationTaskStatus(value) == GenerationTaskStatus.PENDING
|
||||
|
||||
def test_none_value_falls_back_to_pending(self):
|
||||
assert GenerationTaskStatus(None) == GenerationTaskStatus.PENDING # type: ignore[arg-type]
|
||||
|
||||
def test_int_value_falls_back_to_pending(self):
|
||||
assert GenerationTaskStatus(123) == GenerationTaskStatus.PENDING # type: ignore[arg-type]
|
||||
|
||||
|
||||
class TestGenerationTaskStatusStrValue:
|
||||
"""枚举值仍为字符串类型,不影响序列化。"""
|
||||
|
||||
def test_value_unchanged(self):
|
||||
assert GenerationTaskStatus.PENDING.value == "pending"
|
||||
assert GenerationTaskStatus.COMPLETED.value == "completed"
|
||||
assert isinstance(GenerationTaskStatus.PENDING, str)
|
||||
Reference in New Issue
Block a user