6496ee3571
build / build (push) Successful in 25s
- 大小预检此前对 adoc/axls 也用目录大小,而在线文档目录大小是容器大小 (实测有 1.40GB 的 adoc,导出 docx 仅 49.5MB)→ 3 个可导出文档被误判 over_limit - axls 转换器的 dws 失败改为抛 DwsCallError,取件器映射成平台错误码 (此前进程无结果退出,平台只看到 artifact_fetch_no_output) - export_failed 改判 retryable:8 路混合时巨型文档服务端导出偶发失败,1/2/4 路与串行必成 - 新增 2 个回归测试 + dws_error_map 1 个断言;文档补并发实测表与回归清单
115 lines
6.2 KiB
Python
115 lines
6.2 KiB
Python
#!/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)),
|
||
|
||
("未分类错误 → 不得默认可重试",
|
||
{"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) |