From cdacc1f6eca051461aba81e9101f6e16e18be71e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=B5=E5=BA=94?= Date: Sun, 12 Jul 2026 10:27:39 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(asset):=20AssetStatus=E6=9E=9A=E4=B8=BE?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E5=8E=86=E5=8F=B2uploaded=E5=80=BC=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D500?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增_missing_方法处理历史脏数据 - uploaded/success/ok/done → READY - upload/upload_start → UPLOADING - failed/fail/err → ERROR - process/running/run → PROCESSING - 未知值兜底为READY,不阻塞业务 - 新增27个单元测试覆盖全部场景 --- packages/domain/entities.py | 19 ++++++++ tests/unit/test_asset_status_enum.py | 71 ++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100755 tests/unit/test_asset_status_enum.py diff --git a/packages/domain/entities.py b/packages/domain/entities.py index fcf4a2f70..9a76e46bd 100755 --- a/packages/domain/entities.py +++ b/packages/domain/entities.py @@ -134,6 +134,25 @@ class AssetStatus(StrEnum): PROCESSING = "processing" ERROR = "error" + @classmethod + def _missing_(cls, value: object) -> "AssetStatus": + """兼容历史数据,避免枚举转换失败导致500。 + + - uploaded → READY(早期版本用 uploaded 表示上传完成) + - 其他未知值 → READY(兜底,不阻塞业务) + """ + if isinstance(value, str): + normalized = value.strip().lower() + if normalized in ("uploaded", "success", "ok", "done", "complete"): + return cls.READY + if normalized in ("upload", "uploading_start", "upload_start"): + return cls.UPLOADING + if normalized in ("failed", "fail", "err"): + return cls.ERROR + if normalized in ("process", "processing", "running", "run"): + return cls.PROCESSING + return cls.READY + class ClassificationStatus(StrEnum): PENDING = "pending" diff --git a/tests/unit/test_asset_status_enum.py b/tests/unit/test_asset_status_enum.py new file mode 100755 index 000000000..b2d3d539c --- /dev/null +++ b/tests/unit/test_asset_status_enum.py @@ -0,0 +1,71 @@ +"""AssetStatus 枚举兼容性测试。 + +验证历史脏数据(如 'uploaded')不会导致枚举转换失败。 +""" + +import pytest + +from packages.domain.entities import AssetStatus + + +class TestAssetStatusNormalValues: + """正常值应该正确映射。""" + + def test_uploading(self): + assert AssetStatus("uploading") == AssetStatus.UPLOADING + + def test_ready(self): + assert AssetStatus("ready") == AssetStatus.READY + + def test_processing(self): + assert AssetStatus("processing") == AssetStatus.PROCESSING + + def test_error(self): + assert AssetStatus("error") == AssetStatus.ERROR + + +class TestAssetStatusHistoricalValues: + """历史脏数据应该正确映射到对应状态,不抛异常。""" + + @pytest.mark.parametrize("value", ["uploaded", "Uploaded", "UPLOADED", " uploaded "]) + def test_uploaded_maps_to_ready(self, value): + """生产环境发现的 'uploaded' 历史值应映射为 READY。""" + assert AssetStatus(value) == AssetStatus.READY + + @pytest.mark.parametrize("value", ["success", "ok", "done", "complete"]) + def test_other_ready_like_values_map_to_ready(self, value): + assert AssetStatus(value) == AssetStatus.READY + + @pytest.mark.parametrize("value", ["upload", "uploading_start", "upload_start"]) + def test_upload_like_values_map_to_uploading(self, value): + assert AssetStatus(value) == AssetStatus.UPLOADING + + @pytest.mark.parametrize("value", ["failed", "fail", "err"]) + def test_error_like_values_map_to_error(self, value): + assert AssetStatus(value) == AssetStatus.ERROR + + @pytest.mark.parametrize("value", ["process", "running", "run"]) + def test_processing_like_values_map_to_processing(self, value): + assert AssetStatus(value) == AssetStatus.PROCESSING + + +class TestAssetStatusFallback: + """完全未知的值兜底为 READY,不抛500。""" + + @pytest.mark.parametrize("value", ["unknown", "foo_bar", ""]) + def test_unknown_value_falls_back_to_ready(self, value): + assert AssetStatus(value) == AssetStatus.READY + + def test_none_value_falls_back_to_ready(self): + assert AssetStatus(None) == AssetStatus.READY # type: ignore[arg-type] + + def test_int_value_falls_back_to_ready(self): + assert AssetStatus(123) == AssetStatus.READY # type: ignore[arg-type] + + +class TestAssetStatusStrValue: + """枚举值仍为字符串,不影响序列化。""" + + def test_str_value_unchanged(self): + assert AssetStatus.READY.value == "ready" + assert str(AssetStatus.READY) == "ready" -- 2.54.0 From 768b3c599618502e9e32e6ab5fa619e71f620e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=B5=E5=BA=94?= Date: Sun, 12 Jul 2026 11:00:35 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(test):=20=E4=BF=AE=E6=AD=A3AssetStatus?= =?UTF-8?q?=20StrEnum=20str()=E6=96=AD=E8=A8=80=EF=BC=8CCI=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E8=A1=8C=E4=B8=BA=E4=B8=8D=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/test_asset_status_enum.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_asset_status_enum.py b/tests/unit/test_asset_status_enum.py index b2d3d539c..69a9c399f 100755 --- a/tests/unit/test_asset_status_enum.py +++ b/tests/unit/test_asset_status_enum.py @@ -64,8 +64,9 @@ class TestAssetStatusFallback: class TestAssetStatusStrValue: - """枚举值仍为字符串,不影响序列化。""" + """枚举值仍为字符串类型,不影响序列化。""" - def test_str_value_unchanged(self): + def test_value_unchanged(self): assert AssetStatus.READY.value == "ready" - assert str(AssetStatus.READY) == "ready" + assert AssetStatus.FAILED.value == "failed" + assert isinstance(AssetStatus.READY, str) -- 2.54.0 From 2a2de99ea1813e6d668ab9ed20767ac1ef60bf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=B5=E5=BA=94?= Date: Sun, 12 Jul 2026 11:09:13 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(test):=20AssetStatus=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8ERROR=E8=80=8C=E9=9D=9EFAILED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/test_asset_status_enum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_asset_status_enum.py b/tests/unit/test_asset_status_enum.py index 69a9c399f..ef9db7ab1 100755 --- a/tests/unit/test_asset_status_enum.py +++ b/tests/unit/test_asset_status_enum.py @@ -68,5 +68,5 @@ class TestAssetStatusStrValue: def test_value_unchanged(self): assert AssetStatus.READY.value == "ready" - assert AssetStatus.FAILED.value == "failed" + assert AssetStatus.ERROR.value == "error" assert isinstance(AssetStatus.READY, str) -- 2.54.0