fix(transfer): clarify execution conflict errors

This commit is contained in:
jxxghp
2026-09-08 22:01:55 +08:00
parent 3331ba43cd
commit d97906f0a9
7 changed files with 70 additions and 171 deletions
+7 -7
View File
@@ -325,14 +325,14 @@ class TransferPlanningOwner(_TransferOwnerBase):
if repository is None or not task_id:
if task.execution_checkpoint is not None:
raise TransferExecutionConflictError(
"绑定执行检查点的整理任务缺少持久状态仓储"
"整理任务暂时无法继续,请稍后再试"
)
return None
snapshot = repository.get_snapshot(task_id=task_id)
if snapshot is None:
if task.execution_checkpoint is not None:
raise TransferExecutionConflictError(
"绑定执行检查点的整理任务缺少持久执行状态"
"整理任务状态暂时无法确认,请刷新整理历史后再试"
)
return None
if snapshot.state is not TransferExecutionState.SETTLING:
@@ -345,11 +345,11 @@ class TransferPlanningOwner(_TransferOwnerBase):
and bound_checkpoint.fingerprint != checkpoint.fingerprint
):
raise TransferExecutionConflictError(
"内存整理任务与持久结算检查点不一致"
"整理任务状态已发生变化,请刷新整理历史后再试"
)
if checkpoint is None:
raise TransferExecutionConflictError(
"settling 整理任务缺少可重放终态检查点"
"整理任务记录不完整,请重新识别文件后再整理"
)
self._TransferChain__assert_owned_lease(task)
payload = checkpoint.payload
@@ -360,7 +360,7 @@ class TransferPlanningOwner(_TransferOwnerBase):
)
except (TypeError, ValueError) as error:
raise TransferExecutionConflictError(
"整理执行检查点缺少确定终态"
"整理任务记录不完整,请重新识别文件后再整理"
) from error
transfer_payload = payload.get("transferinfo")
if isinstance(transfer_payload, dict):
@@ -388,7 +388,7 @@ class TransferPlanningOwner(_TransferOwnerBase):
)
else:
raise TransferExecutionConflictError(
"成功整理执行检查点缺少可重放 TransferInfo"
"整理任务记录不完整,请重新识别文件后再整理"
)
expected_success = outcome is TransferExecutionOutcome.SUCCEEDED
expected_overwrite_skip = (
@@ -399,7 +399,7 @@ class TransferPlanningOwner(_TransferOwnerBase):
or bool(transferinfo.overwrite_skipped) != expected_overwrite_skip
):
raise TransferExecutionConflictError(
"整理执行检查点终态与 TransferInfo 不一致"
"整理任务状态已发生变化,请刷新整理历史后再试"
)
task.bind_execution_checkpoint(checkpoint)
return transferinfo
+2 -2
View File
@@ -917,13 +917,13 @@ class TransferQueueOwner(_TransferOwnerBase):
raise RuntimeError("整理恢复缺少 execution repository")
snapshot = repository.get_snapshot(task_id=admission.task_id)
if snapshot is None:
raise TransferExecutionConflictError("整理恢复任务缺少执行状态投影")
raise TransferExecutionConflictError("整理恢复状态不完整,请重新识别文件后再整理")
if (
snapshot.state is TransferExecutionState.SETTLING
and snapshot.checkpoint is None
):
raise TransferExecutionConflictError(
"settling 整理任务缺少可重放终态检查点"
"整理任务记录不完整,请重新识别文件后再整理"
)
return snapshot
+1 -1
View File
@@ -126,7 +126,7 @@ class TransferSettlementOwner(_TransferOwnerBase):
and frozen_transferinfo != transferinfo.model_dump(mode="json")
):
raise TransferExecutionConflictError(
"整理终态与冻结 TransferInfo 不一致"
"整理任务状态已发生变化,请刷新整理历史后再试"
)
return TransferResultSettlement(
task_id=task.admission_task_id,
+43 -43
View File
@@ -98,7 +98,7 @@ class TransactionalTransferExecutionRepository:
def _project_step(step: TransferExecutionStepModel) -> TransferExecutionStep:
"""在 Session 有效期内冻结 ORM 步骤为 Application DTO。"""
if (step.result_version is None) != (step.result_payload is None):
raise TransferExecutionConflictError("整理步骤结果版本与 payload 不完整")
raise TransferExecutionConflictError("整理步骤记录不完整,请重新识别文件后再整理")
result = (
TransferStepResult(
version=step.result_version,
@@ -148,7 +148,7 @@ class TransactionalTransferExecutionRepository:
and pending.execution_fingerprint is not None
)
if has_checkpoint_column != has_checkpoint_identity:
raise TransferExecutionConflictError("整理执行检查点列不完整")
raise TransferExecutionConflictError("整理任务记录不完整,请重新识别文件后再整理")
checkpoint = None
if pending.execution_payload is not None:
checkpoint = TransferExecutionCheckpoint.from_payload(
@@ -156,7 +156,7 @@ class TransactionalTransferExecutionRepository:
fingerprint=pending.execution_fingerprint or "",
)
if checkpoint.version != pending.execution_version:
raise TransferExecutionConflictError("整理执行检查点列版本不一致")
raise TransferExecutionConflictError("整理任务记录版本不一致,请重新识别文件后再整理")
return TransferExecutionSnapshot(
task_id=pending.task_id,
state=TransferExecutionState(pending.execution_state),
@@ -201,9 +201,9 @@ class TransactionalTransferExecutionRepository:
and step.updated_at == pending.reviewed_at
]
else:
raise TransferExecutionConflictError("整理任务不属于公开人工复核状态")
raise TransferExecutionConflictError("整理任务当前不需要人工复核")
if not candidates:
raise TransferExecutionConflictError("人工复核任务缺少对应步骤证据")
raise TransferExecutionConflictError("人工复核信息不完整,请重新打开整理记录")
step = max(candidates, key=lambda item: (item.ordinal, item.id))
evidence = dict(step.result_payload) if step.result_payload is not None else None
return TransferManualReviewTaskView(
@@ -250,32 +250,32 @@ class TransactionalTransferExecutionRepository:
TRANSFER_ADMISSION_PLANNED,
TRANSFER_ADMISSION_PROVIDER_PENDING,
}:
raise TransferExecutionConflictError("整理任务尚未进入可执行规划状态")
raise TransferExecutionConflictError("整理任务尚未准备完成,请稍后再试")
if (
pending.checkpoint_version is None
or pending.checkpoint_payload is None
or pending.planned_at is None
):
raise TransferExecutionConflictError("整理任务缺少完整计划检查点")
raise TransferExecutionConflictError("整理任务记录不完整,请重新识别文件后再整理")
try:
checkpoint = TransferPlanCheckpoint.from_payload(
pending.checkpoint_payload
)
except (TypeError, ValueError) as error:
raise TransferExecutionConflictError(
"整理任务计划检查点无法恢复"
"整理任务记录已损坏,请重新识别文件后再整理"
) from error
if pending.checkpoint_version != checkpoint.schema_version:
raise TransferExecutionConflictError("整理任务计划检查点版本不一致")
raise TransferExecutionConflictError("整理任务记录版本不一致,请重新识别文件后再整理")
if checkpoint.planning_input.fingerprint != pending.input_fingerprint:
raise TransferExecutionConflictError("整理任务计划与准入输入指纹不一致")
raise TransferExecutionConflictError("整理任务状态已发生变化,请刷新整理历史后再试")
expected_state = (
TRANSFER_ADMISSION_PROVIDER_PENDING
if checkpoint.is_provider_pending
else TRANSFER_ADMISSION_PLANNED
)
if pending.state != expected_state:
raise TransferExecutionConflictError("整理任务计划类型与准入状态不一致")
raise TransferExecutionConflictError("整理任务状态已发生变化,请刷新整理历史后再试")
return checkpoint, checkpoint.fingerprint
@staticmethod
@@ -477,10 +477,10 @@ class TransactionalTransferExecutionRepository:
) -> None:
"""验证全部持久步骤属于冻结计划演进且身份与全局顺序未被篡改。"""
if tuple(step.ordinal for step in steps) != tuple(range(len(steps))):
raise TransferExecutionConflictError("整理步骤全局序号不连续")
raise TransferExecutionConflictError("整理任务正在被其他操作处理,请稍后再试")
for index, step in enumerate(steps):
if step.task_id != task_id:
raise TransferExecutionConflictError("整理步骤绑定了错误任务")
raise TransferExecutionConflictError("整理任务状态已发生变化,请刷新整理历史后再试")
step_checkpoint = checkpoint
if step.checkpoint_fingerprint != checkpoint_fingerprint:
predecessor = cls._provider_predecessor_checkpoint(
@@ -492,7 +492,7 @@ class TransactionalTransferExecutionRepository:
or step.checkpoint_fingerprint != predecessor.fingerprint
):
raise TransferExecutionConflictError(
"整理步骤不属于当前冻结计划或合法 provider 前驱计划"
"整理任务记录已失效,请重新识别文件后再整理"
)
step_checkpoint = predecessor
if not cls._intent_belongs_to_checkpoint(
@@ -503,7 +503,7 @@ class TransactionalTransferExecutionRepository:
previous_steps=steps[:index],
):
raise TransferExecutionConflictError(
"整理步骤类型或参数不能由冻结计划导出"
"整理任务记录已失效,请重新识别文件后再整理"
)
expected_operation_id = build_transfer_operation_id(
task_id=task_id,
@@ -514,7 +514,7 @@ class TransactionalTransferExecutionRepository:
intent_payload=step.intent_payload,
)
if step.operation_id != expected_operation_id:
raise TransferExecutionConflictError("整理步骤 operation ID 与冻结意图不一致")
raise TransferExecutionConflictError("整理任务记录已失效,请重新识别文件后再整理")
@classmethod
def _validate_new_intent(
@@ -534,7 +534,7 @@ class TransactionalTransferExecutionRepository:
steps=steps,
)
if intent.checkpoint_fingerprint != checkpoint_fingerprint:
raise TransferExecutionConflictError("整理步骤意图未绑定当前冻结计划指纹")
raise TransferExecutionConflictError("整理任务记录已失效,请重新识别文件后再整理")
if not cls._intent_belongs_to_checkpoint(
checkpoint,
phase=intent.phase,
@@ -543,7 +543,7 @@ class TransactionalTransferExecutionRepository:
previous_steps=steps,
):
raise TransferExecutionConflictError(
"整理步骤意图类型或参数不能由冻结计划导出"
"整理任务记录已失效,请重新识别文件后再整理"
)
expected_operation_id = build_transfer_operation_id(
task_id=task_id,
@@ -554,7 +554,7 @@ class TransactionalTransferExecutionRepository:
intent_payload=intent.payload,
)
if intent.operation_id != expected_operation_id:
raise TransferExecutionConflictError("整理步骤意图 operation ID 不可信")
raise TransferExecutionConflictError("整理任务记录已失效,请重新识别文件后再整理")
existing = next(
(step for step in steps if step.operation_id == intent.operation_id),
None,
@@ -562,11 +562,11 @@ class TransactionalTransferExecutionRepository:
if existing is not None:
if not cls._intent_matches(existing, task_id=task_id, intent=intent):
raise TransferExecutionConflictError(
"稳定 operation ID 已绑定不同步骤意图"
"整理任务正在被其他操作处理,请稍后再试"
)
return
if intent.ordinal != len(steps):
raise TransferExecutionConflictError("整理步骤意图必须按全局序号连续追加")
raise TransferExecutionConflictError("整理任务记录已失效,请重新识别文件后再整理")
@staticmethod
def _require_active_lease(
@@ -759,7 +759,7 @@ class TransactionalTransferExecutionRepository:
session.flush()
elif not self._intent_matches(step, task_id=task_id, intent=intent):
raise TransferExecutionConflictError(
"稳定 operation ID 已绑定不同步骤意图"
"整理任务正在被其他操作处理,请稍后再试"
)
projected = self._project_step(step)
transaction.commit()
@@ -767,7 +767,7 @@ class TransactionalTransferExecutionRepository:
except IntegrityError as error:
self._rollback(transaction)
raise TransferExecutionConflictError(
"整理步骤 operation ID 或全局序号发生并发冲突"
"整理任务正在被其他操作处理,请稍后再试"
) from error
except Exception:
self._rollback(transaction)
@@ -805,7 +805,7 @@ class TransactionalTransferExecutionRepository:
session.expire_all()
step = oper.get_by_operation_id(operation_id=operation_id)
if step is None:
raise TransferExecutionConflictError("整理步骤开始后无法回读")
raise TransferExecutionConflictError("整理任务执行状态暂时无法确认,请刷新整理历史后再试")
projected = self._project_step(step)
transaction.commit()
return projected
@@ -850,7 +850,7 @@ class TransactionalTransferExecutionRepository:
session.expire_all()
step = oper.get_by_operation_id(operation_id=operation_id)
if step is None:
raise TransferExecutionConflictError("整理步骤重启后无法回读")
raise TransferExecutionConflictError("整理任务执行状态暂时无法确认,请刷新整理历史后再试")
projected = self._project_step(step)
transaction.commit()
return projected
@@ -891,7 +891,7 @@ class TransactionalTransferExecutionRepository:
)
if operation_id not in {step.operation_id for step in steps}:
raise TransferExecutionConflictError(
"待恢复步骤不属于冻结计划"
"整理任务记录已失效,请重新识别文件后再整理"
)
pending_updated = pending_oper.stage_execution_running(
task_id=task_id,
@@ -927,7 +927,7 @@ class TransactionalTransferExecutionRepository:
session.expire_all()
step = oper.get_by_operation_id(operation_id=operation_id)
if step is None:
raise TransferExecutionConflictError("恢复重试后无法回读整理步骤")
raise TransferExecutionConflictError("整理任务执行状态暂时无法确认,请刷新整理历史后再试")
projected = self._project_step(step)
transaction.commit()
return projected
@@ -970,7 +970,7 @@ class TransactionalTransferExecutionRepository:
session.expire_all()
step = oper.get_by_operation_id(operation_id=operation_id)
if step is None:
raise TransferExecutionConflictError("整理步骤完成后无法回读")
raise TransferExecutionConflictError("整理任务执行状态暂时无法确认,请刷新整理历史后再试")
projected = self._project_step(step)
transaction.commit()
return projected
@@ -1034,7 +1034,7 @@ class TransactionalTransferExecutionRepository:
session.expire_all()
pending = pending_oper.get_by_task_id(task_id=task_id)
if pending is None:
raise TransferExecutionConflictError("重试任务无法回读")
raise TransferExecutionConflictError("整理任务执行状态暂时无法确认,请刷新整理历史后再试")
snapshot = self._project_snapshot(
pending,
step_oper.list_by_task_id(task_id=task_id),
@@ -1122,7 +1122,7 @@ class TransactionalTransferExecutionRepository:
session.expire_all()
pending = pending_oper.get_by_task_id(task_id=task_id)
if pending is None:
raise TransferExecutionConflictError("失败结算任务无法回读")
raise TransferExecutionConflictError("整理失败状态暂时无法确认,请刷新整理历史后再试")
snapshot = self._project_snapshot(pending, steps)
transaction.commit()
return snapshot
@@ -1145,7 +1145,7 @@ class TransactionalTransferExecutionRepository:
oper = TransferPendingOper(session)
pending = oper.get_by_task_id(task_id=task_id)
if pending is None:
raise TransferExecutionConflictError("未找到可重试的整理任务")
raise TransferExecutionConflictError("这条整理任务已不存在或已经处理完成")
state = TransferExecutionState(pending.execution_state)
if state is TransferExecutionState.RETRY_WAIT:
return TransferRetryRequestResult(
@@ -1177,7 +1177,7 @@ class TransactionalTransferExecutionRepository:
session.expire_all()
pending = oper.get_by_task_id(task_id=task_id)
if pending is None:
raise TransferExecutionConflictError("重试请求竞争后任务已不存在")
raise TransferExecutionConflictError("这条整理任务已被其他操作处理,请刷新整理历史确认最新状态")
state = TransferExecutionState(pending.execution_state)
if state is TransferExecutionState.RETRY_WAIT:
return TransferRetryRequestResult(
@@ -1196,7 +1196,7 @@ class TransactionalTransferExecutionRepository:
session.expire_all()
pending = oper.get_by_task_id(task_id=task_id)
if pending is None:
raise TransferExecutionConflictError("登记重试后任务无法回读")
raise TransferExecutionConflictError("整理重试状态暂时无法确认,请刷新整理历史后再试")
result = TransferRetryRequestResult(
accepted=True,
state=TransferExecutionState.RETRY_WAIT,
@@ -1301,7 +1301,7 @@ class TransactionalTransferExecutionRepository:
)
if detached != 1:
raise TransferExecutionConflictError(
"失败整理任务删除后无法解除历史回执映射"
"整理失败记录清理未完成,请刷新整理历史后再试"
)
transaction.commit()
return TransferFailureDiscardResult(
@@ -1326,7 +1326,7 @@ class TransactionalTransferExecutionRepository:
"""在无 lease 短事务中原子提交步骤与 pending 的人工判定。"""
if decision is TransferManualReviewDecision.FAILED:
raise TransferExecutionConflictError(
"人工失败终态尚不能绕过 lease durable 结算"
"该整理任务需要通过正常流程完成结算,请重新整理"
)
if decision is TransferManualReviewDecision.APPLIED and result is None:
raise ValueError("人工判定已发生时必须提供结果证据")
@@ -1348,7 +1348,7 @@ class TransactionalTransferExecutionRepository:
and step.kind == _LEGACY_REVIEW_STEP_KIND
):
raise TransferExecutionConflictError(
"升级遗留步骤没有足够证据证明外部操作已发生"
"这条整理步骤无法确认是否已经执行,请人工确认文件状态后再继续"
"请先人工回滚,或确认未发生后选择 NOT_APPLIED"
)
step_updated = step_oper.stage_resolve_manual_review(
@@ -1362,7 +1362,7 @@ class TransactionalTransferExecutionRepository:
)
if step_updated != 1:
raise TransferExecutionConflictError(
"步骤已不处于可判定的无租约人工复核态"
"步骤当前无法进行人工确认,请刷新整理历史后再试"
)
pending_oper = TransferPendingOper(session)
pending_updated = pending_oper.stage_resolve_manual_review(
@@ -1375,14 +1375,14 @@ class TransactionalTransferExecutionRepository:
)
if pending_updated != 1:
raise TransferExecutionConflictError(
"任务已不处于可判定的无租约人工复核态"
"该整理任务当前无法进行人工确认,请刷新整理历史后再试"
)
session.flush()
session.expire_all()
pending = pending_oper.get_by_task_id(task_id=task_id)
step = step_oper.get_by_operation_id(operation_id=operation_id)
if pending is None or step is None:
raise TransferExecutionConflictError("人工判定提交后无法回读")
raise TransferExecutionConflictError("人工确认结果暂时无法保存,请刷新整理历史后再试")
resolved = TransferManualReviewResult(
task_id=task_id,
operation_id=operation_id,
@@ -1451,7 +1451,7 @@ class TransactionalTransferExecutionRepository:
session.expire_all()
pending = pending_oper.get_by_task_id(task_id=task_id)
if pending is None:
raise TransferExecutionConflictError("人工复核任务无法回读")
raise TransferExecutionConflictError("人工复核状态暂时无法确认,请刷新整理历史后再试")
snapshot = self._project_snapshot(
pending,
step_oper.list_by_task_id(task_id=task_id),
@@ -1495,11 +1495,11 @@ class TransactionalTransferExecutionRepository:
step_ids = tuple(step.operation_id for step in steps)
if step_ids != checkpoint.operation_ids:
raise TransferExecutionConflictError(
"执行检查点引用的步骤顺序与持久步骤不一致"
"整理任务状态已发生变化,请刷新整理历史后再试"
)
if any(step.state != TransferStepState.SUCCEEDED.value for step in steps):
raise TransferExecutionConflictError(
"存在未成功步骤,不能提交执行检查点"
"整理任务仍有步骤未完成,请先完成前置步骤"
)
running = pending_oper.stage_execution_running(
task_id=task_id,
@@ -1537,7 +1537,7 @@ class TransactionalTransferExecutionRepository:
session.expire_all()
pending = pending_oper.get_by_task_id(task_id=task_id)
if pending is None:
raise TransferExecutionConflictError("执行检查点任务无法回读")
raise TransferExecutionConflictError("整理任务执行状态暂时无法确认,请刷新整理历史后再试")
snapshot = self._project_snapshot(pending, steps)
transaction.commit()
return snapshot
+5 -101
View File
@@ -1,4 +1,4 @@
"""把内部错误转换为可直接展示给用户的简短文案"""
"""提供统一的错误文本规范化,不改变业务层已经生成的错误含义"""
from typing import Literal, Optional
@@ -18,94 +18,6 @@ _CONTEXT_FALLBACKS: dict[PublicErrorContext, str] = {
"outbox": "后台任务暂未完成,系统会自动重试",
}
_TECHNICAL_MARKERS = (
"outbox",
"durable",
"checkpoint",
"provider_pending",
"schema_version",
"operation_id",
"operation id",
"attempt_token",
"attempt token",
"lease_token",
"transferinfo",
"dispatcher",
"traceback",
"runtimeerror",
"valueerror",
"typeerror",
"keyerror",
"attributeerror",
"operationalerror",
"connectionerror",
"timeout",
"client error",
"api error",
"connecterror",
"connect error",
"connection",
"errno",
"http ",
"http/",
"max retries",
"retryerror",
"response status",
"sslerror",
"ssl error",
"status code",
"too many requests",
"server error",
"for url",
"object at 0x",
"no streaming chunk",
"streaming chunk",
"failed",
"检查点",
"步骤意图",
"意图",
"事实",
"证据",
"租约",
"副作用",
"执行凭证",
"指纹",
"回执",
)
def _normalize_error(error: Optional[object]) -> str:
"""压缩异常中的换行和多余空白,避免内部格式污染界面。"""
if error is None:
return ""
return " ".join(str(error).split())
def _contains_technical_marker(message: str) -> bool:
"""判断文案是否包含只适合日志或开发者排查的实现术语。"""
normalized = message.lower()
return any(marker.lower() in normalized for marker in _TECHNICAL_MARKERS)
def _context_fallback(
context: PublicErrorContext,
message: str,
fallback: str | None,
) -> str:
"""根据业务上下文选择不泄露内部细节的兜底文案。"""
if fallback:
return fallback
normalized = message.lower()
if "订阅" in message or context == "subscription":
return _CONTEXT_FALLBACKS["subscription"]
if "智能助手" in message or "ai智能体" in normalized:
return "智能助手执行失败,请稍后重试"
if "整理" in message or context == "transfer":
return _CONTEXT_FALLBACKS["transfer"]
if "outbox" in normalized or "durable" in normalized or context == "outbox":
return _CONTEXT_FALLBACKS["outbox"]
return _CONTEXT_FALLBACKS[context]
def public_error_message(
error: Optional[object],
@@ -113,16 +25,8 @@ def public_error_message(
context: PublicErrorContext = "generic",
fallback: Optional[str] = None,
) -> str:
"""将内部异常或状态转换为人类可理解的前台提示。
只有明确识别为内部实现信息的文本才会被替换;普通的业务提示会原样保留,
这样既能统一异常出口,又不会丢失“目录不存在”等可执行的处理建议。
原始异常应继续写入日志或结构化诊断字段,不应作为本函数的返回值直接展示。
"""
message = _normalize_error(error)
if not message:
"""返回业务层提供的错误文案,仅清理空白并为空值提供兜底。"""
if error is None:
return fallback or _CONTEXT_FALLBACKS[context]
if _contains_technical_marker(message):
return _context_fallback(context, message, fallback)
return message
message = " ".join(str(error).split())
return message or fallback or _CONTEXT_FALLBACKS[context]
+5 -10
View File
@@ -27,24 +27,19 @@ def test_transfer_status_uses_human_readable_messages(source: str, expected: str
assert public_error_message(source, context="transfer") == expected
def test_technical_transfer_message_is_hidden() -> None:
"""整理检查点、操作身份等内部信息应统一降级为可执行提示"""
def test_public_error_message_preserves_source_wording() -> None:
"""错误文本由业务源头负责生成,公共函数只做空白规范化"""
message = public_error_message(
"整理步骤意图 operation_id=op-1 的 checkpoint evidence 无效",
context="transfer",
)
assert message == "整理失败,请刷新后重试"
assert "意图" not in message
assert "checkpoint" not in message
assert message == "整理步骤意图 operation_id=op-1 的 checkpoint evidence 无效"
def test_subscription_status_does_not_expose_provider_timeout() -> None:
"""订阅执行状态不应把服务商和超时实现细节展示给前端。"""
assert (
public_error_message("provider timeout", context="subscription")
== "订阅操作失败,请刷新后重试"
)
assert public_error_message("provider timeout", context="subscription") == "provider timeout"
def test_post_commit_failure_explains_background_retry() -> None:
@@ -78,7 +73,7 @@ def test_transfer_history_hides_internal_error_in_nested_public_data() -> None:
errmsg="整理步骤意图 operation_id=op-1 的 checkpoint evidence 无效",
)
assert history.errmsg == "整理失败,请刷新后重试"
assert history.errmsg == "整理步骤意图 operation_id=op-1 的 checkpoint evidence 无效"
def test_post_commit_error_keeps_background_retry_message() -> None:
+7 -7
View File
@@ -292,7 +292,7 @@ def test_prepare_rejects_noncanonical_plan_fingerprint(execution_store):
kind="materialize_target",
payload=_intent().payload,
)
with pytest.raises(TransferExecutionConflictError, match="当前冻结计划指纹"):
with pytest.raises(TransferExecutionConflictError, match="整理任务记录已失效"):
command.prepare(
task_id="task-1",
lease_token="lease-1",
@@ -313,7 +313,7 @@ def test_prepare_rejects_forged_operation_id(execution_store):
kind=valid.kind,
payload=valid.payload,
)
with pytest.raises(TransferExecutionConflictError, match="operation ID 不可信"):
with pytest.raises(TransferExecutionConflictError, match="整理任务记录已失效"):
command.prepare(
task_id="task-1",
lease_token="lease-1",
@@ -339,7 +339,7 @@ def test_prepare_rejects_arbitrary_intent_with_known_plan_fingerprint(
"target_path": "/media/task-1.mkv",
},
)
with pytest.raises(TransferExecutionConflictError, match="冻结计划导出"):
with pytest.raises(TransferExecutionConflictError, match="整理任务记录已失效"):
command.prepare(
task_id="task-1",
lease_token="lease-1",
@@ -351,7 +351,7 @@ def test_prepare_rejects_noncontiguous_ordinal(execution_store):
"""新步骤只能在完整既有序列尾部连续追加。"""
_seed_pending(execution_store)
_, command = _repository(execution_store)
with pytest.raises(TransferExecutionConflictError, match="连续追加"):
with pytest.raises(TransferExecutionConflictError, match="整理任务记录已失效"):
command.prepare(
task_id="task-1",
lease_token="lease-1",
@@ -615,7 +615,7 @@ def test_checkpoint_rejects_operation_ids_out_of_ordinal_order(execution_store):
payload={"outcome": "succeeded", "dest": "/media/task-1.mkv"},
operation_ids=tuple(step.operation_id for step in reversed(completed)),
)
with pytest.raises(TransferExecutionConflictError, match="步骤顺序"):
with pytest.raises(TransferExecutionConflictError, match="整理任务状态已发生变化"):
command.checkpoint(
task_id="task-1",
lease_token="lease-1",
@@ -652,7 +652,7 @@ def test_checkpoint_rejects_corrupted_persisted_operation_id(execution_store):
payload={"outcome": "succeeded", "dest": "/media/task-1.mkv"},
operation_ids=("e" * 64,),
)
with pytest.raises(TransferExecutionConflictError, match="冻结意图"):
with pytest.raises(TransferExecutionConflictError, match="整理任务记录已失效"):
command.checkpoint(
task_id="task-1",
lease_token="lease-1",
@@ -698,7 +698,7 @@ def test_checkpoint_rejects_noncontiguous_persisted_ordinals(execution_store):
payload={"outcome": "succeeded", "dest": "/media/task-1.mkv"},
operation_ids=(corrupted_operation_id,),
)
with pytest.raises(TransferExecutionConflictError, match="全局序号不连续"):
with pytest.raises(TransferExecutionConflictError, match="整理任务正在被其他操作处理"):
command.checkpoint(
task_id="task-1",
lease_token="lease-1",