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