Files
knowledge-governance/scripts/test_dws_error_map.py
T
姚章浩 f4e1ecc5f1 feat(artifact): 钉钉取件优先走 dws(DingTalk Workspace CLI),老路径保留为回退
- artifact_fetch.py: 新增 dingtalk dws 取件器(adoc→doc export、axls→sheet 转换、其余→drive 直下),
  ARTIFACT_DINGTALK_FETCHER=dws|auto|legacy 三级开关,auto 仅对可重试错误回落老路径;
  平台侧判定(pick_suffix/SLIDES/magic/大小上限/sha256/spool/空壳检查)全部保留;
  结果 JSON 字段不变(Go runner 用 DisallowUnknownFields 解析 stdout),取件器来源只写 stderr。
- axls_to_xlsx.py: 纯标准库 axls→xlsx(文本写入 sharedStrings 以通过空壳判定)。
- dws_error_map.py: dws 错误→平台 {ok,code,retryable,missing};未分类错误默认不重试。
- Dockerfile: alpine 增加 gcompat/libc6-compat;dws v1.0.63 固定版本+sha256 校验;构建期 dws version 自检。
- compose.yaml: worker 增加 dws 环境与凭据挂载(DWS_HOME 方案,XDG_DATA_HOME 不被 dws 识别)。
- 测试: artifact_fetch_test 新增 11 项(路由/回落语义/契约字段锁定);dws_error_map 17 断言。
- 实测: 真容器(--read-only --tmpfs --user 1000:1000) 端到端 adoc/axls/pdf 3/3 成功,docx sha 与平台基准一致。
2026-10-09 10:57:11 +08:00

106 lines
5.6 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 sys
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)),
("未分类错误 → 不得默认可重试",
{"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)