Files
knowledge-governance/scripts/test_dws_error_map.py
T
姚章浩 9155bd3170
build / build (push) Successful in 23s
fix(artifact): 类型误标回退 + 服务端阻断判可重试 + 空白单元格重读
压测/取件实测暴露的三类问题(均在真实 worker 容器里复现并已验证修复):
- 名为 .axls 实为 docx 的节点(清单扩展名与真实类型不符):dws 明确回报真实类型
  → 回退 drive download 取原始字节(实测该节点 243,949B docx 可正常取到),
  不再直接失败
- server model operate block 判可重试:同一节点 5 连测 3/5 成功(另一支 timeout),
  原判终态会把可成功节点永久失败
- axls 大表偶发返回全空单元格(12 次取件出现 1 次):全空时重读一次再上报,
  避免被平台空壳判定当「空文档」终态

新增 3 个回归测试(18 tests / 19 断言全绿)
2026-10-09 12:15:22 +08:00

120 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""dws_error_map 单测:全部使用本轮实测捕获的真实 dws 错误体(不改写、不简化)。"""
import json
import os
import sys
# 测的是仓库里的实现(脚本目录),bridge/ 只是本轮评估期的开发副本
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
if not os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "dws_error_map.py")):
sys.path.insert(0, "/home/zheromiku/dws_test/bridge")
from dws_error_map import classify
# —— 真实样本(逐字来自本轮会话的工具输出)——
CASES = [
("TLS 缺 CA(debian-slim 实测)",
{"category": "api",
"cause": 'Post "https://mcp-gw.dingtalk.com/server/91e17caf44f6ca1ed9c6ce614221a518ac93300ece63ca8d7e9b133f912e0607": tls: failed to verify certificate: x509: certificate signed by unknown authority',
"code": 1, "hint": "请检查网络连通性和 MCP 服务状态后重试。", "retryable": True,
"message": "提交导出任务失败: request to https://mcp-gw.dingtalk.com failed"},
("tls_ca", False, False)),
("登录态被顶掉(容器认证包轮换实测)",
{"type": "internal", "exit_code": 5, "upstream_code": "UNCLASSIFIED",
"message": "[UNCLASSIFIED] resolve access token: 旧版登录态已无法由当前认证服务刷新;本地 profile 已保留,请重新运行 dws auth login 完成一次重新授权: MCP token exchange failed: invalidParameter.authCode.notFound - 不合法的临时授权码"},
("credentials", False, False)),
("无权限(技术诊断部某 axls)",
{"error": {"actions": ["持续失败时保留 Trace ID 和 Server Code 联系服务端排查"], "category": "api", "code": 1,
"hint": "The API returned a business-level error.", "message": "Access Denied",
"operation": "tools/call", "reason": "business_error",
"server_error_code": "forbidden.accessDenied", "server_key": "sheet"}},
("forbidden", False, False)),
("严格版 512MB 上限(838MB xz 实测)",
{"type": "internal", "exit_code": 5, "message": "LOCAL_DOWNLOAD_TOO_LARGE: 响应大小 878540264 超过上限 536870912 字节"},
("over_limit", False, False)),
("本地文件已存在(探针重跑实测)",
{"type": "internal", "exit_code": 5, "message": "LOCAL_FILE_EXISTS: 目标文件已存在;请选择新的输出路径"},
("file_exists", False, False)),
("able 节点走 doc 入口(实测拒收)",
{"error": {"category": "api", "code": 1,
"message": "提交导出任务失败: 当前节点是钉钉AI表格(多维表,扩展名=able),不是钉钉在线文档(扩展名=adoc),无法使用文档相关工具操作。请改用钉钉AI表格相关 MCP Tool 或相关 CLI 命令进行操作。",
"server_error_code": "invalidRequest.inputArgs.invalid", "server_key": "doc"}},
("unsupported_format", False, False)),
("axls 不支持直下(实测拒收)",
{"type": "api", "subtype": "business_error", "exit_code": 1,
"upstream_code": "invalidRequest.inputArgs.invalid",
"message": "nodeId 指向的节点是钉钉表格(extension=axls),在线表格不支持直接下载。请使用 getRange 工具获取表格数据。"},
("unsupported_format", False, False)),
("节点不存在(dlink 节点实测)",
{"ok": False, "outcome": "failure",
"error": {"type": "api", "subtype": "business_error", "exit_code": 1,
"upstream_code": "internalError", "message": "workspace node not found"}},
("missing", False, True)),
("正文为空(平台历史缺陷文案)",
{"category": "api", "code": 1, "message": "DingTalk online document content is empty"},
("empty_document", False, False)),
("限流(构造,用于锁定 429 语义)",
{"category": "api", "code": 1, "message": "HTTP 429 Too Many Requests"},
("http_429", True, False)),
("连接层失败(构造,锁定可重试语义)",
{"category": "api", "code": 1, "cause": "dial tcp: lookup alidocs.dingtalk.com: no such host"},
("source_unavailable", True, False)),
("导出任务超时(构造,锁定可重试语义)",
{"category": "api", "code": 1, "message": "DingTalk export job timed out after 120s"},
("export_timeout", True, False)),
("导出任务失败(并发压测实测:同一节点 8 路偶发失败、1/2/4 路及串行必成)",
{"error": {"category": "api", "code": 1, "message": "提交导出任务失败: 钉钉导出任务失败",
"server_error_code": "internalError"}},
("export_failed", True, False)),
("服务端阻断(5 连测实测:同一节点 3/5 成功,另一支是 timeout)",
{"error": {"category": "api", "code": 1, "message": "server model operate block",
"server_error_code": "model.operate.block", "reason": "business_error"}},
("upstream_blocked", True, False)),
("未分类错误 → 不得默认可重试",
{"category": "internal", "code": 9, "message": "something nobody has seen before"},
("unknown", False, False)),
]
SUCCESS_CASES = [
("严格版成功体", {"ok": True, "outcome": "success", "data": {"count": 1}}),
("普通子命令成功体", {"success": True, "sizeBytes": 305078, "status": "SUCCESS"}),
("drive 成功体", {"ok": True, "outcome": "success", "data": {"savedPath": "ref.xlsx", "sizeBytes": 3583939, "success": True}}),
]
fails = 0
print("=== 失败体映射 ===")
for label, payload, expect in CASES:
r = classify(payload)
got = (r["code"], r["retryable"], r["missing"])
mark = "✅" if got == expect else "❌"
if got != expect:
fails += 1
print(f" {mark} {label:34s} → code={r['code']:<18} retryable={str(r['retryable']):<5} missing={str(r['missing']):<5} 期望={expect}")
print("\n=== 成功体映射 ===")
for label, payload in SUCCESS_CASES:
r = classify(payload)
ok = r["ok"] is True and r["code"] == ""
if not ok:
fails += 1
print(f" {'✅' if ok else '❌'} {label:34s} → ok={r['ok']} code={r['code']!r}")
print("\n=== 文本输入(stderr 兜底)===")
r = classify(None, stderr="Error: x509: certificate signed by unknown authority")
print(f" {'✅' if r['code']=='tls_ca' else '❌'} stderr-only 文本 → code={r['code']}")
fails += 0 if r["code"] == "tls_ca" else 1
print(f"\n结果: {'全部通过' if fails==0 else str(fails)+' 项失败'}({len(CASES)+len(SUCCESS_CASES)+1} 个断言)")
sys.exit(1 if fails else 0)