fix(api): finish public error message rollout

This commit is contained in:
jxxghp
2026-09-04 18:21:44 +08:00
parent ff01449cdd
commit f5f534dcad
12 changed files with 44 additions and 20 deletions
+5 -1
View File
@@ -168,7 +168,11 @@ class FailedRetryMixin(_TransferOwnerBase):
state, errmsg = self.redo_transfer_history(history_id)
if state:
public_message = public_error_message(errmsg, context="transfer")
public_message = (
public_error_message(errmsg, context="transfer")
if errmsg
else ""
)
self.post_message(
Message(
channel=channel,
+5 -4
View File
@@ -1,8 +1,9 @@
from typing import List, Literal, Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
from pydantic import BaseModel, ConfigDict, Field
from pydantic import field_validator as _field_validator
from app.runtime.errors import public_error_message
from app.runtime.errors import public_error_message as _public_error_message
from app.schemas.common import JsonData
from app.schemas.media import OptionalMediaIdentityMixin
from app.schemas.types import MediaSource
@@ -149,13 +150,13 @@ class TransferHistory(OptionalMediaIdentityMixin, BaseModel):
model_config = ConfigDict(from_attributes=True)
@field_validator("errmsg", mode="before")
@_field_validator("errmsg", mode="before")
@classmethod
def _sanitize_error_message(cls, value: object) -> Optional[str]:
"""历史接口只返回可理解的整理失败原因,数据库原文仍用于诊断。"""
if value is None or not str(value).strip():
return None
return public_error_message(value, context="transfer")
return _public_error_message(value, context="transfer")
class BatchTransferHistoryRedoRequest(BaseModel):
+2 -2
View File
@@ -2,7 +2,7 @@ from typing import Any, Generic, Optional, TypeVar
from pydantic import BaseModel, ConfigDict, field_validator
from app.runtime.errors import public_error_message
from app.runtime.errors import public_error_message as _public_error_message
from app.runtime.localization import LocaleHelper
DataT = TypeVar("DataT")
@@ -32,7 +32,7 @@ class Response(BaseModel, Generic[DataT]):
raw_message = str(value)
if not raw_message.strip():
return ""
message = public_error_message(raw_message)
message = _public_error_message(raw_message)
if not message:
return ""
return LocaleHelper.translate_text(
+1 -1
View File
@@ -755,7 +755,7 @@ flowchart LR
| 指标 | 当前值 |
|---|---:|
| Python 模块 | 968 |
| 内部导入边 | 8,141 |
| 内部导入边 | 8,156 |
| 非平凡 SCC | 1(精确 containment 的 TMDB 移植包环) |
| Application / Chain 具体 Adapter 直连 | 0 / 0 |
| Direct egress | 53(债务已清零,53 条精确 containment |
+1 -1
View File
@@ -94,7 +94,7 @@ ARCH-201 至 ARCH-204 均达到实现、验证、提交、推送和远端门禁
| 指标 | 当前值 | 解释 |
|---|---:|---|
| 宿主 Python 模块 / 内部依赖边 | 968 / 8,141 | `dependency-baseline.json` 当前快照 |
| 宿主 Python 模块 / 内部依赖边 | 969 / 8,156 | `dependency-baseline.json` 当前快照 |
| 非平凡 SCC | 1 | 仅保留精确 containment 的 29 模块 TMDB 移植包环 |
| 跨层 DB 边界债务 | 0 | Application、Chain、API、Agent、Runtime、Workflow 到 DB 的受控债务均为零 |
| Model/Oper 事务债务 | 0 | 自建 Session、自动事务装饰器、直接 commit/rollback 等基线均为零 |
+19 -3
View File
@@ -1089,8 +1089,8 @@
"runtime_only": true
}
},
"edge_count": 8141,
"edge_sha256": "f86580389ab2c9efd50914fdbd34004fbdc3e9b01ad06a537817415add7c3687",
"edge_count": 8156,
"edge_sha256": "9b9596b5e25de2e68a007dfb3934f027458a6741cfc2df1e92e936b7ffca0d1a",
"edges": [
"app -> app.foundation",
"app -> app.foundation.environment",
@@ -2275,6 +2275,7 @@
"app.api.endpoints.history -> app.application.transfer",
"app.api.endpoints.history -> app.application.transfer.execution",
"app.api.endpoints.history -> app.runtime",
"app.api.endpoints.history -> app.runtime.errors",
"app.api.endpoints.history -> app.runtime.log",
"app.api.endpoints.history -> app.runtime.loop",
"app.api.endpoints.history -> app.runtime.progress",
@@ -2713,7 +2714,9 @@
"app.api.endpoints.subscribe -> app.domain.context",
"app.api.endpoints.subscribe -> app.domain.metainfo",
"app.api.endpoints.subscribe -> app.runtime",
"app.api.endpoints.subscribe -> app.runtime.errors",
"app.api.endpoints.subscribe -> app.runtime.execution",
"app.api.endpoints.subscribe -> app.runtime.log",
"app.api.endpoints.subscribe -> app.runtime.tasks",
"app.api.endpoints.subscribe -> app.schemas",
"app.api.endpoints.subscribe -> app.schemas.common",
@@ -2833,6 +2836,7 @@
"app.api.endpoints.transfer -> app.chain.transfer",
"app.api.endpoints.transfer -> app.chain.transfer.facade",
"app.api.endpoints.transfer -> app.runtime",
"app.api.endpoints.transfer -> app.runtime.errors",
"app.api.endpoints.transfer -> app.runtime.log",
"app.api.endpoints.transfer -> app.runtime.stop",
"app.api.endpoints.transfer -> app.schemas",
@@ -2950,6 +2954,7 @@
"app.api.servarr -> app.domain.context",
"app.api.servarr -> app.domain.metainfo",
"app.api.servarr -> app.runtime",
"app.api.servarr -> app.runtime.errors",
"app.api.servarr -> app.runtime.version",
"app.api.servarr -> app.schemas",
"app.api.servarr -> app.schemas.response",
@@ -3644,6 +3649,8 @@
"app.application.subscription.status -> app.application",
"app.application.subscription.status -> app.application.subscription",
"app.application.subscription.status -> app.application.subscription.execution",
"app.application.subscription.status -> app.runtime",
"app.application.subscription.status -> app.runtime.errors",
"app.application.subscription.write -> app.application",
"app.application.subscription.write -> app.application.classification",
"app.application.subscription.write -> app.application.classification.reference",
@@ -4984,6 +4991,7 @@
"app.chain.transfer.history -> app.domain.meta",
"app.chain.transfer.history -> app.domain.meta.metabase",
"app.chain.transfer.history -> app.runtime",
"app.chain.transfer.history -> app.runtime.errors",
"app.chain.transfer.history -> app.runtime.log",
"app.chain.transfer.history -> app.schemas",
"app.chain.transfer.history -> app.schemas.message",
@@ -5093,6 +5101,7 @@
"app.chain.transfer.retry -> app.domain",
"app.chain.transfer.retry -> app.domain.context",
"app.chain.transfer.retry -> app.runtime",
"app.chain.transfer.retry -> app.runtime.errors",
"app.chain.transfer.retry -> app.runtime.log",
"app.chain.transfer.retry -> app.runtime.loop",
"app.chain.transfer.retry -> app.runtime.tasks",
@@ -5132,6 +5141,7 @@
"app.chain.transfer.settlement -> app.domain.meta",
"app.chain.transfer.settlement -> app.domain.meta.metabase",
"app.chain.transfer.settlement -> app.runtime",
"app.chain.transfer.settlement -> app.runtime.errors",
"app.chain.transfer.settlement -> app.runtime.log",
"app.chain.transfer.settlement -> app.runtime.loop",
"app.chain.transfer.settlement -> app.schemas",
@@ -5903,6 +5913,7 @@
"app.factory -> app.api",
"app.factory -> app.api.response",
"app.factory -> app.application",
"app.factory -> app.application.outbox",
"app.factory -> app.application.plugin",
"app.factory -> app.application.plugin.routes",
"app.factory -> app.application.plugin.runtime",
@@ -5910,6 +5921,7 @@
"app.factory -> app.application.security.token",
"app.factory -> app.runtime",
"app.factory -> app.runtime.correlation",
"app.factory -> app.runtime.errors",
"app.factory -> app.runtime.localization",
"app.factory -> app.runtime.log",
"app.factory -> app.runtime.loop",
@@ -8097,6 +8109,8 @@
"app.schemas.event -> app.schemas.types",
"app.schemas.file -> app.schemas",
"app.schemas.file -> app.schemas.types",
"app.schemas.history -> app.runtime",
"app.schemas.history -> app.runtime.errors",
"app.schemas.history -> app.schemas",
"app.schemas.history -> app.schemas.common",
"app.schemas.history -> app.schemas.media",
@@ -8130,6 +8144,7 @@
"app.schemas.query -> app.schemas.media",
"app.schemas.query -> app.schemas.types",
"app.schemas.response -> app.runtime",
"app.schemas.response -> app.runtime.errors",
"app.schemas.response -> app.runtime.localization",
"app.schemas.search -> app.schemas",
"app.schemas.search -> app.schemas.common",
@@ -9234,7 +9249,7 @@
"app.workflow.actions.transfer_file -> app.workflow",
"app.workflow.actions.transfer_file -> app.workflow.actions"
],
"module_count": 968,
"module_count": 969,
"modules": [
"app",
"app.adapters",
@@ -10003,6 +10018,7 @@
"app.runtime.deprecation",
"app.runtime.deprecation.notices",
"app.runtime.deprecation.policy",
"app.runtime.errors",
"app.runtime.event",
"app.runtime.event.binding",
"app.runtime.event.contracts",
+1 -1
View File
@@ -325,7 +325,7 @@ async def test_agent_execution_failure_closes_cached_subagent_middleware() -> No
result, _ = await agent._execute_agent([])
assert "failed" in result
assert result == "智能助手执行失败,请稍后重试"
middleware.close.assert_awaited_once()
assert agent._compiled_agent_bundle is None
assert agent._subagent_middlewares == ()
+4 -1
View File
@@ -35,6 +35,7 @@ from app.application.outbox import (
DOWNLOAD_MODULE_TOPIC,
DOWNLOAD_NOTIFICATION_TOPIC,
DOWNLOAD_SUBTITLE_TOPIC,
PostCommitEffectError,
)
from app.application.transfer.execution import (
TransferExecutionCheckpoint,
@@ -561,7 +562,7 @@ def test_transfer_event_failure_leaves_committed_intent_pending():
"""模拟插件事件总线在业务提交后失败。"""
raise RuntimeError("event failed")
with pytest.raises(RuntimeError, match="event failed"):
with pytest.raises(PostCommitEffectError, match="提交后的相关处理未完成") as error:
writer.transfer_result(
topic="transfer.completed",
stage_history=stage_history,
@@ -576,6 +577,8 @@ def test_transfer_event_failure_leaves_committed_intent_pending():
},
publish=fail_publish,
)
assert len(error.value.errors) == 1
assert str(error.value.errors[0]) == "event failed"
with factory() as session:
history = session.execute(select(TransferHistory)).scalar_one()
+1 -1
View File
@@ -51,7 +51,7 @@ def test_update_cookie_legacy_get_keeps_query_params():
)
assert response.success is False
assert response.message == "failed"
assert response.message == "操作失败,请稍后重试"
fake_chain.update_cookie.assert_called_once_with(
site_info=fake_site,
username="user",
+3 -3
View File
@@ -186,7 +186,7 @@ def test_add_rejects_incomplete_media_identity(identity):
):
result = _add(mediainfo=_media(None), season=1)
assert result == (0, "媒体身份不完整")
assert result == (0, "未识别到媒体信息,请检查媒体来源和媒体 ID 后重试")
# 守卫必须在查询与建模之前短路,而不是先写进去再补救
subscribe_model.exists.assert_not_called()
subscribe_model.assert_not_called()
@@ -203,7 +203,7 @@ def test_async_add_rejects_incomplete_media_identity(identity):
result = asyncio.run(_async_add(mediainfo=_media(None), season=1))
assert result == (0, "媒体身份不完整")
assert result == (0, "未识别到媒体信息,请检查媒体来源和媒体 ID 后重试")
subscribe_model.async_exists.assert_not_awaited()
subscribe_model.assert_not_called()
@@ -571,7 +571,7 @@ def test_subscribe_exists_distinguishes_music_entities_with_same_source_id(db):
def test_subscribe_chain_exists_forwards_episode_group():
"""订阅前置存在性检查必须查询当前剧集组,不能退回主季范围。"""
from app.chain.subscribe import SubscribeChain
from app.chain.subscribe.facade import SubscribeChain
media = _media("eg-1")
meta = SimpleNamespace(begin_season=1)
@@ -9,7 +9,7 @@ from app.application.transfer.execution import (
TransferExecutionCheckpoint,
TransferSettlementResult,
)
from app.chain.transfer import TransferChain
from app.chain.transfer.facade import TransferChain
from app.domain.context import MediaInfo
from app.domain.meta.metabase import MetaBase
from app.schemas.file import FileItem
+1 -1
View File
@@ -195,5 +195,5 @@ def test_manual_review_conflict_returns_http_409(monkeypatch) -> None:
)
assert error.value.status_code == 409
assert error.value.detail == "步骤已被判定"
assert error.value.detail == "整理任务状态已变化,请刷新后重试"
assert _ManualReviewCommand.calls[0][1]["actor"] == "11"