WREMOTE1 完成远端收口验证

这个提交包含在:
2026-05-22 14:08:42 +08:00
父节点 6361dc2efb
当前提交 91dbeadb02
共修改 5 个文件,包含 52 行新增和 10 行删除
@@ -5,6 +5,6 @@
"WREMOTE1-3","准备 WREAL1 临时 env 模板","在 /tmp/hyb-oa-wreal1.env 准备真实试用临时配置占位,供用户后续填写真实员工和审批人/观察人,不进仓库。","1) 文件只在 /tmp;2) 只包含注释和空值占位;3) 不输出真实员工号;4) 后续只校验非空和脱敏。","受影响流程:WREAL1 启动前人工配置 -> 系统脱敏读取;验证步骤:test -f /tmp/hyb-oa-wreal1.env;awk -F= '/^WREAL1_/ {print $1""=""($2==""""?""EMPTY"":""SET"")}' /tmp/hyb-oa-wreal1.env 确认只输出 key 和 SET/EMPTY,不输出真实员工号。","bash","DONE","DONE","DONE","/tmp/hyb-oa-wreal1.env","[""WREMOTE1-2""]","回滚/清理:删除 /tmp/hyb-oa-wreal1.env;该文件不纳入 git。"
"WREMOTE1-4","只读 reviewer 审查远端与正式模板边界","涉及审批规则、权限和真实试用准入,实施后由只读 reviewer 检查是否可能误用本地结果、误授权企微写入或跳过人工门禁。","1) reviewer 只读;2) 审查 WFULLSMOKE2 本地默认阻断;3) 审查 WEXT1-FORMAL 不创建模板、不自动提交审批;4) 审查 WREAL1 信息不入仓库;5) blocker 必须修复。","受影响流程:脚本/文档修正 -> 独立评审 -> 门禁回填;验证步骤:reviewer subagent 输出无 blocker 或 blocker 已处理。","subagent|manual","DONE","DONE","DONE","N/A","[""WREMOTE1-3""]","回滚/清理:reviewer 不修改文件;如发现风险,暂停部署和远端执行。"
"WREMOTE1-5","本地静态门禁复跑","复跑合同、process gate、脚本语法、文档审计、skill 校验、diff 检查和最小回归。","1) validate_issues_csv 通过;2) baseline 和 issue-specific process gate 通过;3) bash -n 通过;4) audit-docs、quick_validate、git diff --check 通过;5) bash scripts/check-minimal.sh 通过。","受影响流程:本地静态门禁 -> 提交 -> 部署;验证步骤:validate_issues_csv.py <csv>;node scripts/qa/check-process-contract.mjs --baseline --json;node scripts/qa/check-process-contract.mjs --issue <csv> --profile wecom --json;bash -n scripts/qa/run-three-day-full-goal-smoke.sh scripts/qa/run-wecom-test-account-final-closeout.sh scripts/qa/collect-wecom-controlled-trial-evidence.sh;bash scripts/check-minimal.sh。","python3|node|bash|git","DONE","DONE","DONE","scripts/qa/run-three-day-full-goal-smoke.sh|scripts/qa/run-wecom-test-account-final-closeout.sh|scripts/qa/collect-wecom-controlled-trial-evidence.sh|docs/*|.agents/skills/hyb-oa-delivery/SKILL.md","[""WREMOTE1-4""]","回滚/清理:若门禁失败,保留 TODO 并暂停部署。"
"WREMOTE1-6","部署并远端执行 WTCLOSE2","本地门禁通过并提交后,使用 SYNC_ENV=0 部署/同步,然后从部署服务器可信 IP 执行 WTCLOSE2 远端收口。","1) SYNC_ENV=0 部署成功;2) /ready 正常;3) bash scripts/qa/run-wecom-test-account-final-closeout.sh --remote 生成远端报告;4) 报告不泄露完整 sp_no/template_id/userid/token/Secret/表单值/原始 payload。","受影响流程:部署 -> 远端 WTCLOSE2 -> WEXT1-FORMAL 问答;验证步骤:SYNC_ENV=0 bash deploy/trialrun/deploy.sh;curl -fsS https://oa.hyinda.com/ready;bash scripts/qa/run-wecom-test-account-final-closeout.sh --remote。","bash|ssh|curl","TODO","TODO","TODO","deploy/trialrun/*|scripts/qa/run-wecom-test-account-final-closeout.sh","[""WREMOTE1-5""]","回滚/清理:若部署或远端 WTCLOSE2 阻断,记录真实 SKIP/BLOCKED_BY_EXTERNAL_CONFIG;不继续正式模板测试。"
"WREMOTE1-7","远端执行 WFULLSMOKE2 全域 smoke","远端运行 WFULLSMOKE2,允许创建 TEST-SMOKE-WFULL 标记的 OA 主办域测试数据,验证全域主路径。","1) 远端执行全域 smoke;2) 只创建 TEST-SMOKE-WFULL 标记 OA 测试业务数据;3) 不写企业微信、不真实付款、不接支付、不写 ERP、不导入银行流水;4) 输出 PASS_FULL_GOAL_SMOKE_READY、PASS_WITH_FINDINGS、PAUSED_MANUAL_CONFIRMATION 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。","受影响流程:OA 报销 -> 采购 -> 合同/项目 -> 付款批次 -> 移动/企微入口;验证步骤:WFULLSMOKE_ALLOW_PUBLIC_WRITES=1 bash scripts/qa/run-three-day-full-goal-smoke.sh --remote;核对 summary.json 和测试标记。","bash|ssh|playwright","TODO","TODO","TODO","scripts/qa/run-three-day-full-goal-smoke.sh","[""WREMOTE1-6""]","回滚/清理:测试数据保留审计,不自动清理;若凭据缺失,记录 PASS_WITH_FINDINGS 或 PAUSED_MANUAL_CONFIRMATION。"
"WREMOTE1-8","提交并报告后续人工门禁","回填 WREMOTE1 状态并中文提交;远端 WTCLOSE2/WFULLSMOKE2 结果决定是否进入正式模板外出/加班人工提交问答。","1) CSV 状态按真实验证回填;2) 中文提交包含 WREMOTE1;3) 最终说明是否已部署、远端报告路径和下一步人工操作;4) 若未远端执行,明确阻断原因。","受影响流程:提交 -> 人工 WEXT1-FORMAL 问答 -> WREAL1;验证步骤:git status --short;git log -1 --oneline。","git","TODO","TODO","TODO","issues/2026-05-22_12-34-41-wecom-remote-closeout-and-full-smoke-v1.csv|scripts/qa/run-three-day-full-goal-smoke.sh|docs/*|.agents/skills/hyb-oa-delivery/SKILL.md","[""WREMOTE1-7""]","回滚/清理:revert 本提交;WEXT1-FORMAL 和 WREAL1 仍需后续人工确认。"
"WREMOTE1-6","部署并远端执行 WTCLOSE2","本地门禁通过并提交后,使用 SYNC_ENV=0 部署/同步,然后从部署服务器可信 IP 执行 WTCLOSE2 远端收口。","1) SYNC_ENV=0 部署成功;2) /ready 正常;3) bash scripts/qa/run-wecom-test-account-final-closeout.sh --remote 生成远端报告;4) 报告不泄露完整 sp_no/template_id/userid/token/Secret/表单值/原始 payload。","受影响流程:部署 -> 远端 WTCLOSE2 -> WEXT1-FORMAL 问答;验证步骤:SYNC_ENV=0 bash deploy/trialrun/deploy.sh;curl -fsS https://oa.hyinda.com/ready;bash scripts/qa/run-wecom-test-account-final-closeout.sh --remote。","bash|ssh|curl","DONE","DONE","DONE","deploy/trialrun/*|scripts/qa/run-wecom-test-account-final-closeout.sh|server/src/services/integrations/wecomApprovalArchive.js|server/tests/unit/services/wecomApprovalArchive.test.js","[""WREMOTE1-5""]","远端部署通过,/ready OK;WTCLOSE2 报告 /app/artifacts/qa-flow/wecom-test-account-final-closeout-20260522-055840/summary.json,结论 SKIP/BLOCKED_BY_EXTERNAL_CONFIG:新补偿归档中请假/补卡各 1 条已可 WRULE1 PASS_RULE_MATCH;历史 callback-only 旧行缺 applicant/approver,外出/加班尚无正式模板归档,需 WEXT1-FORMAL 人工提交后复核;隐私扫描通过。回滚/清理:恢复脚本和解析修正;不写企微。"
"WREMOTE1-7","远端执行 WFULLSMOKE2 全域 smoke","远端运行 WFULLSMOKE2,允许创建 TEST-SMOKE-WFULL 标记的 OA 主办域测试数据,验证全域主路径。","1) 远端执行全域 smoke;2) 只创建 TEST-SMOKE-WFULL 标记 OA 测试业务数据;3) 不写企业微信、不真实付款、不接支付、不写 ERP、不导入银行流水;4) 输出 PASS_FULL_GOAL_SMOKE_READY、PASS_WITH_FINDINGS、PAUSED_MANUAL_CONFIRMATION 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。","受影响流程:OA 报销 -> 采购 -> 合同/项目 -> 付款批次 -> 移动/企微入口;验证步骤:WFULLSMOKE_ALLOW_PUBLIC_WRITES=1 bash scripts/qa/run-three-day-full-goal-smoke.sh --remote;核对 summary.json 和测试标记。","bash|ssh|playwright","DONE","DONE","DONE","scripts/qa/run-three-day-full-goal-smoke.sh","[""WREMOTE1-6""]","WFULLSMOKE2 远端报告 /app/artifacts/qa-flow/three-day-full-goal-smoke-20260522-055953/summary.json,结论 PASS_WITH_FINDINGS;/ready、WARCH3、WAUTO1、隐私扫描通过;OA public write flows 因缺 LIVE_SMOKE_* 业务凭据未创建测试数据;移动/企微入口 spec 未打入运行镜像,记录 finding;未写企业微信。"
"WREMOTE1-8","提交并报告后续人工门禁","回填 WREMOTE1 状态并中文提交;远端 WTCLOSE2/WFULLSMOKE2 结果决定是否进入正式模板外出/加班人工提交问答。","1) CSV 状态按真实验证回填;2) 中文提交包含 WREMOTE1;3) 最终说明是否已部署、远端报告路径和下一步人工操作;4) 若未远端执行,明确阻断原因。","受影响流程:提交 -> 人工 WEXT1-FORMAL 问答 -> WREAL1;验证步骤:git status --short;git log -1 --oneline。","git","DONE","DONE","DONE","issues/2026-05-22_12-34-41-wecom-remote-closeout-and-full-smoke-v1.csv|scripts/qa/run-three-day-full-goal-smoke.sh|scripts/qa/run-wecom-test-account-final-closeout.sh|server/src/services/integrations/wecomApprovalArchive.js|server/tests/unit/services/wecomApprovalArchive.test.js","[""WREMOTE1-7""]","本轮回填远端报告路径和真实结论;WEXT1-FORMAL 不自动开始,需用户按 A/B/C 选择是否由 SMOKE002 人工提交正式模板外出+加班测试单。回滚/清理:恢复本轮 CSV 状态和脚本解析/报告口径修正;不删除远端报告,正式模板人工测试另行授权。"
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
5 WREMOTE1-3 准备 WREAL1 临时 env 模板 在 /tmp/hyb-oa-wreal1.env 准备真实试用临时配置占位,供用户后续填写真实员工和审批人/观察人,不进仓库。 1) 文件只在 /tmp;2) 只包含注释和空值占位;3) 不输出真实员工号;4) 后续只校验非空和脱敏。 受影响流程:WREAL1 启动前人工配置 -> 系统脱敏读取;验证步骤:test -f /tmp/hyb-oa-wreal1.env;awk -F= '/^WREAL1_/ {print $1"="($2==""?"EMPTY":"SET")}' /tmp/hyb-oa-wreal1.env 确认只输出 key 和 SET/EMPTY,不输出真实员工号。 bash DONE DONE DONE /tmp/hyb-oa-wreal1.env ["WREMOTE1-2"] 回滚/清理:删除 /tmp/hyb-oa-wreal1.env;该文件不纳入 git。
6 WREMOTE1-4 只读 reviewer 审查远端与正式模板边界 涉及审批规则、权限和真实试用准入,实施后由只读 reviewer 检查是否可能误用本地结果、误授权企微写入或跳过人工门禁。 1) reviewer 只读;2) 审查 WFULLSMOKE2 本地默认阻断;3) 审查 WEXT1-FORMAL 不创建模板、不自动提交审批;4) 审查 WREAL1 信息不入仓库;5) blocker 必须修复。 受影响流程:脚本/文档修正 -> 独立评审 -> 门禁回填;验证步骤:reviewer subagent 输出无 blocker 或 blocker 已处理。 subagent|manual DONE DONE DONE N/A ["WREMOTE1-3"] 回滚/清理:reviewer 不修改文件;如发现风险,暂停部署和远端执行。
7 WREMOTE1-5 本地静态门禁复跑 复跑合同、process gate、脚本语法、文档审计、skill 校验、diff 检查和最小回归。 1) validate_issues_csv 通过;2) baseline 和 issue-specific process gate 通过;3) bash -n 通过;4) audit-docs、quick_validate、git diff --check 通过;5) bash scripts/check-minimal.sh 通过。 受影响流程:本地静态门禁 -> 提交 -> 部署;验证步骤:validate_issues_csv.py <csv>;node scripts/qa/check-process-contract.mjs --baseline --json;node scripts/qa/check-process-contract.mjs --issue <csv> --profile wecom --json;bash -n scripts/qa/run-three-day-full-goal-smoke.sh scripts/qa/run-wecom-test-account-final-closeout.sh scripts/qa/collect-wecom-controlled-trial-evidence.sh;bash scripts/check-minimal.sh。 python3|node|bash|git DONE DONE DONE scripts/qa/run-three-day-full-goal-smoke.sh|scripts/qa/run-wecom-test-account-final-closeout.sh|scripts/qa/collect-wecom-controlled-trial-evidence.sh|docs/*|.agents/skills/hyb-oa-delivery/SKILL.md ["WREMOTE1-4"] 回滚/清理:若门禁失败,保留 TODO 并暂停部署。
8 WREMOTE1-6 部署并远端执行 WTCLOSE2 本地门禁通过并提交后,使用 SYNC_ENV=0 部署/同步,然后从部署服务器可信 IP 执行 WTCLOSE2 远端收口。 1) SYNC_ENV=0 部署成功;2) /ready 正常;3) bash scripts/qa/run-wecom-test-account-final-closeout.sh --remote 生成远端报告;4) 报告不泄露完整 sp_no/template_id/userid/token/Secret/表单值/原始 payload。 受影响流程:部署 -> 远端 WTCLOSE2 -> WEXT1-FORMAL 问答;验证步骤:SYNC_ENV=0 bash deploy/trialrun/deploy.sh;curl -fsS https://oa.hyinda.com/ready;bash scripts/qa/run-wecom-test-account-final-closeout.sh --remote。 bash|ssh|curl TODO DONE TODO DONE TODO DONE deploy/trialrun/*|scripts/qa/run-wecom-test-account-final-closeout.sh deploy/trialrun/*|scripts/qa/run-wecom-test-account-final-closeout.sh|server/src/services/integrations/wecomApprovalArchive.js|server/tests/unit/services/wecomApprovalArchive.test.js ["WREMOTE1-5"] 回滚/清理:若部署或远端 WTCLOSE2 阻断,记录真实 SKIP/BLOCKED_BY_EXTERNAL_CONFIG;不继续正式模板测试。 远端部署通过,/ready OK;WTCLOSE2 报告 /app/artifacts/qa-flow/wecom-test-account-final-closeout-20260522-055840/summary.json,结论 SKIP/BLOCKED_BY_EXTERNAL_CONFIG:新补偿归档中请假/补卡各 1 条已可 WRULE1 PASS_RULE_MATCH;历史 callback-only 旧行缺 applicant/approver,外出/加班尚无正式模板归档,需 WEXT1-FORMAL 人工提交后复核;隐私扫描通过。回滚/清理:恢复脚本和解析修正;不写企微。
9 WREMOTE1-7 远端执行 WFULLSMOKE2 全域 smoke 远端运行 WFULLSMOKE2,允许创建 TEST-SMOKE-WFULL 标记的 OA 主办域测试数据,验证全域主路径。 1) 远端执行全域 smoke;2) 只创建 TEST-SMOKE-WFULL 标记 OA 测试业务数据;3) 不写企业微信、不真实付款、不接支付、不写 ERP、不导入银行流水;4) 输出 PASS_FULL_GOAL_SMOKE_READY、PASS_WITH_FINDINGS、PAUSED_MANUAL_CONFIRMATION 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。 受影响流程:OA 报销 -> 采购 -> 合同/项目 -> 付款批次 -> 移动/企微入口;验证步骤:WFULLSMOKE_ALLOW_PUBLIC_WRITES=1 bash scripts/qa/run-three-day-full-goal-smoke.sh --remote;核对 summary.json 和测试标记。 bash|ssh|playwright TODO DONE TODO DONE TODO DONE scripts/qa/run-three-day-full-goal-smoke.sh ["WREMOTE1-6"] 回滚/清理:测试数据保留审计,不自动清理;若凭据缺失,记录 PASS_WITH_FINDINGS 或 PAUSED_MANUAL_CONFIRMATION。 WFULLSMOKE2 远端报告 /app/artifacts/qa-flow/three-day-full-goal-smoke-20260522-055953/summary.json,结论 PASS_WITH_FINDINGS;/ready、WARCH3、WAUTO1、隐私扫描通过;OA public write flows 因缺 LIVE_SMOKE_* 业务凭据未创建测试数据;移动/企微入口 spec 未打入运行镜像,记录 finding;未写企业微信。
10 WREMOTE1-8 提交并报告后续人工门禁 回填 WREMOTE1 状态并中文提交;远端 WTCLOSE2/WFULLSMOKE2 结果决定是否进入正式模板外出/加班人工提交问答。 1) CSV 状态按真实验证回填;2) 中文提交包含 WREMOTE1;3) 最终说明是否已部署、远端报告路径和下一步人工操作;4) 若未远端执行,明确阻断原因。 受影响流程:提交 -> 人工 WEXT1-FORMAL 问答 -> WREAL1;验证步骤:git status --short;git log -1 --oneline。 git TODO DONE TODO DONE TODO DONE issues/2026-05-22_12-34-41-wecom-remote-closeout-and-full-smoke-v1.csv|scripts/qa/run-three-day-full-goal-smoke.sh|docs/*|.agents/skills/hyb-oa-delivery/SKILL.md issues/2026-05-22_12-34-41-wecom-remote-closeout-and-full-smoke-v1.csv|scripts/qa/run-three-day-full-goal-smoke.sh|scripts/qa/run-wecom-test-account-final-closeout.sh|server/src/services/integrations/wecomApprovalArchive.js|server/tests/unit/services/wecomApprovalArchive.test.js ["WREMOTE1-7"] 回滚/清理:revert 本提交;WEXT1-FORMAL 和 WREAL1 仍需后续人工确认。 本轮回填远端报告路径和真实结论;WEXT1-FORMAL 不自动开始,需用户按 A/B/C 选择是否由 SMOKE002 人工提交正式模板外出+加班测试单。回滚/清理:恢复本轮 CSV 状态和脚本解析/报告口径修正;不删除远端报告,正式模板人工测试另行授权。
+5 -4
查看文件
@@ -305,8 +305,8 @@ write_manual_checklist() {
## Day 2
- SMOKE002:继续按企微规则打卡。
- HYD000006:检查请假、补卡、外出、加班四类模板审批规则。
- WAPPR1 选项:A 审批人自动带出且申请人不能任意选择;B 默认带出但申请人仍可改人;C 必须申请人手选;D 当前看不到或模板未配置。只有 A 放行。
- HYD000006:已在 2026-05-21 真机确认请假、补卡、外出、加班四类模板均为 A。
- WAPPR1 当前结论:A 审批人自动带出且申请人不能任意选择;申请人不能删除、改人或新增审批人。若后续模板配置变化,B/C/D 均重新阻断 WEXT1-FORMAL 和 WREAL1。
- 小团队规则:公司不到 10 人,默认一层直属主管或指定审批角色自动带出审批人;主管链缺失、审批人不可见/停用/未绑定或命中自审时,由管理员兜底或阻断。
## Day 3
@@ -437,9 +437,9 @@ const manualGates = [
},
{
id: "WAPPR1",
status: "PENDING_MANUAL_CONFIRMATION",
status: "PASS_APPROVER_RULES_READY",
owner: "HYD000006",
requiredEvidence: "四类模板均为 A:一层主管/指定审批角色自动带出,申请人不能任意选择;缺主管/不可用/自审时管理员兜底或阻断",
requiredEvidence: "2026-05-21 用户真机确认请假、补卡、外出、加班均为 A:审批人自动带出,申请人不能删除、改人或新增审批人",
},
{
id: "WEXT1-FORMAL",
@@ -466,6 +466,7 @@ if (Number(statusInput || 0) !== 0 || findings.some((item) => item.severity ===
const summary = {
issueId: "WFULLSMOKE1",
routeIssueId: "WFULLSMOKE2",
mode: "three-day-full-goal-smoke",
status,
conclusion: status,
@@ -213,8 +213,9 @@ run_wrule1() {
run_privacy_smoke() {
local sensitive_pattern='(["'\'']?(WECOM_[A-Z0-9_]*(SECRET|TOKEN)|[A-Z0-9_]*(TOKEN|SECRET|PASSWORD|ENCODING_AES_KEY|ENCODINGAESKEY|CORP_SECRET|ACCESS_TOKEN)[A-Z0-9_]*)["'\'']?[[:space:]]*[:=][[:space:]]*["'\'']?[A-Za-z0-9._:/+=-]{8,}["'\'']?)|Authorization:[[:space:]]*Bearer[[:space:]]+[A-Za-z0-9._-]{8,}|access_token=[A-Za-z0-9._-]{8,}|1[3-9][0-9]{9}|[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}|-----BEGIN|BEGIN [A-Z ]*KEY|"[^"]*(token|secret|password|encodingAESKey|encoding_aes_key|sp_no|spNo|template_id|templateId|userid|userId|external_userid|externalUserId|external_user_id)[^"]*"[[:space:]]*:[[:space:]]*"[^"]{8,}"|\b20[0-9]{12,}\b|"(raw|rawPayload|raw_payload|payload|xml|ciphertext|Encrypt|formValue|formValues|attachments|comments)"[[:space:]]*:'
local safe_metadata_pattern='"[^"]*(secret|token)[^"]*"[[:space:]]*:[[:space:]]*"WECOM_[A-Z0-9_]*(SECRET|TOKEN)([[:space:]]|["]|$)|"[^"]*(secret|token)[^"]*"[[:space:]]*:[[:space:]]*"WECOM_[A-Z0-9_]*(SECRET|TOKEN)[^"]*(source|path|read paths|fallback)'
local safe_redacted_prefix_pattern='"[^"]*(sp_no|spNo|template_id|templateId|userid|userId|external_userid|externalUserId|external_user_id)[^"]*Prefixes?"[[:space:]]*:[[:space:]]*"[^"]{1,16}\*\*\*[^"]{0,8}"'
local matches
matches="$(grep -RInEi "$sensitive_pattern" "$REPORT_DIR" | grep -vE 'HRWDTEST-[0-9]{14}' | grep -vEi "$safe_metadata_pattern" || true)"
matches="$(grep -RInEi "$sensitive_pattern" "$REPORT_DIR" | grep -vE 'HRWDTEST-[0-9]{14}' | grep -vEi "$safe_metadata_pattern" | grep -vEi "$safe_redacted_prefix_pattern" || true)"
if [[ -n "$matches" ]]; then
printf '%s\n' "$matches"
log "[wtclose2][FAIL] report-privacy-smoke matched sensitive pattern"
@@ -164,10 +164,20 @@ function normalizeFields(info) {
.filter((item) => item.field_key);
}
function extractUserId(value) {
function extractUserId(value, depth = 0) {
if (!value) return "";
if (typeof value === "string") return toSafeString(value, 128);
return pickText(value, ["userid", "user_id", "UserId", "UserID", "open_userid", "OpenUserId"], 128);
if (depth > 3 || typeof value !== "object") return "";
const direct = pickText(value, ["userid", "user_id", "UserId", "UserID", "open_userid", "OpenUserId"], 128);
if (direct) return direct;
for (const key of ["approver", "user", "member", "operator", "handler", "applyer", "applicant", "creator"]) {
const nested = extractUserId(value[key], depth + 1);
if (nested) return nested;
}
return "";
}
function normalizeParticipants(info) {
@@ -111,6 +111,36 @@ describe("wecomApprovalArchive", () => {
expect(detail.detail_digest).toMatch(/^[0-9a-f]{64}$/);
});
it("normalizes nested approval record approvers", () => {
const nestedApprover = "nested_approver_full_userid_should_not_leak";
const detail = normalizeApprovalDetailPayload(approvalPayload({
sp_record: [{
details: [{
approver: { userid: nestedApprover },
sp_status: "2",
speech_time: "1779149800",
speech: "同意",
}],
}],
}), {
source: "compensation_pull",
target: "leave",
});
expect(detail.participants).toEqual(expect.arrayContaining([
expect.objectContaining({
role: "approver",
external_user_id: nestedApprover,
}),
]));
expect(detail.actions).toEqual(expect.arrayContaining([
expect.objectContaining({
action_type: "approve",
operator_external_user_id: nestedApprover,
}),
]));
});
it("returns only masked public archive summaries", () => {
const summary = publicArchiveSummary({
id: "archive-1",