#!/usr/bin/env bash set -u -o pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" SERVER_DIR="$ROOT_DIR/server" PUBLIC_BASE_URL="${LIVE_SMOKE_BASE_URL:-https://oa.hyinda.com}" REPORT_DIR="${WROAD2_OPS_REPORT_DIR:-$ROOT_DIR/artifacts/qa-flow/wroad2-ops-$(date +%Y%m%d-%H%M%S)}" SUMMARY_TXT="$REPORT_DIR/summary.txt" STATUS=0 FINDING_COUNT=0 mkdir -p "$REPORT_DIR" : >"$SUMMARY_TXT" load_default_env() { local env_file="${WROAD2_OPS_ENV_FILE:-$HOME/.hyb-oa/live-smoke.env}" if [[ "${WROAD2_OPS_SKIP_ENV_FILE:-0}" == "1" || ! -f "$env_file" ]]; then return 0 fi set -a # shellcheck disable=SC1090 source "$env_file" set +a PUBLIC_BASE_URL="${LIVE_SMOKE_BASE_URL:-$PUBLIC_BASE_URL}" echo "[wroad2-ops] loaded masked env file" | tee -a "$SUMMARY_TXT" } run_step() { local name="$1" shift local log_file="$REPORT_DIR/${name}.log" echo ">>> [wroad2-ops] $name" | tee -a "$SUMMARY_TXT" if "$@" > >(tee "$log_file") 2>&1; then echo "[wroad2-ops][PASS] $name" | tee -a "$SUMMARY_TXT" else local code="$?" echo "[wroad2-ops][FAIL] $name exit=$code" | tee -a "$SUMMARY_TXT" STATUS=1 fi } run_finding_step() { local name="$1" shift local log_file="$REPORT_DIR/${name}.log" echo ">>> [wroad2-ops] $name" | tee -a "$SUMMARY_TXT" if "$@" > >(tee "$log_file") 2>&1; then echo "[wroad2-ops][PASS] $name" | tee -a "$SUMMARY_TXT" else local code="$?" echo "[wroad2-ops][FINDING] $name exit=$code" | tee -a "$SUMMARY_TXT" FINDING_COUNT=$((FINDING_COUNT + 1)) fi } run_shell_step() { local name="$1" local command="$2" run_step "$name" bash -lc "$command" } run_shell_finding_step() { local name="$1" local command="$2" run_finding_step "$name" bash -lc "$command" } write_summary_json() { local status="PASS_CONTROLLED_OPS_READY" local qa_status="PASS" local notes="WROAD2+OPS Day 0/每日巡检通过:受控上线测试账号范围,归档补偿可写 OA 留存库,不写企业微信,不安装定时任务。" if [[ "$STATUS" -ne 0 ]]; then status="SKIP/BLOCKED_BY_EXTERNAL_CONFIG" qa_status="SKIP" notes="WROAD2+OPS 巡检存在失败:按真实 Secret、权限、可信 IP、可见范围、凭据或外部配置阻断处理,不伪造成 PASS。" elif [[ "$FINDING_COUNT" -gt 0 ]]; then status="PASS_WITH_FINDINGS" qa_status="PASS_WITH_FINDINGS" notes="WROAD2+OPS 巡检主链路通过,但存在可解释 finding;按事实源保留审计,不扩大范围。" fi QA_REPORT_DIR="$REPORT_DIR" \ QA_ISSUE_ID="WROAD2" \ QA_BASE_URL="$PUBLIC_BASE_URL" \ QA_MODE="wroad2-controlled-ops-daily" \ QA_STATUS="$qa_status" \ QA_SUMMARY_NOTES="$notes" \ node "$ROOT_DIR/scripts/qa/write-qa-summary.mjs" >/dev/null 2>&1 || true node - "$REPORT_DIR" "$PUBLIC_BASE_URL" "$status" "$notes" "$FINDING_COUNT" <<'NODE' || true const fs = require("node:fs"); const path = require("node:path"); const { execSync } = require("node:child_process"); const [reportDir, baseURL, initialStatus, initialNotes, findingCountInput] = process.argv.slice(2); const findingCount = Number(findingCountInput || 0); let commit = "unknown"; try { commit = execSync("git rev-parse --short HEAD", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim(); } catch {} const childReports = {}; for (const [key, file] of Object.entries({ liveReadiness: "live-smoke-readiness/summary.json", wecomRuntimeReady: "wecom-runtime-ready/summary.json", approvalCompensation: "wecom-approval-compensation-poc/summary.json", approvalArchive: "wecom-approval-archive/summary.json", })) { const fullPath = path.join(reportDir, file); if (!fs.existsSync(fullPath)) continue; try { const json = JSON.parse(fs.readFileSync(fullPath, "utf8")); childReports[key] = { status: json.status || json.conclusion || "UNKNOWN", conclusion: json.conclusion || null, reportPath: fullPath, }; } catch { childReports[key] = { status: "UNREADABLE", reportPath: fullPath }; } } const childStatuses = Object.values(childReports).map((report) => report.status); const status = initialStatus === "PASS_CONTROLLED_OPS_READY" && childStatuses.includes("PASS_WITH_FINDINGS") ? "PASS_WITH_FINDINGS" : initialStatus; const notes = status === "PASS_WITH_FINDINGS" ? "WROAD2+OPS 巡检主链路通过,但子报告存在可解释 finding;按事实源保留审计,不扩大范围。" : initialNotes; const summary = { issueId: "WROAD2", mode: "wroad2-controlled-ops-daily", status, baseURL, commit, checkedAt: new Date().toISOString(), reportDir: path.resolve(reportDir), pilot: { scope: "test_accounts", applicant: "SMOKE002", approver: "SMOKE101", reviewer: "wangshunli", durationDays: 3, schedule: "manual_daily_script", }, boundaries: { noWeComWrites: true, noFixtureCreation: true, noCronOrSystemdTimer: true, archivePersistence: "OA_DB_only_with_process_level_WECOM_ARCHIVE_WRITE", postponed: ["outing_overtime", "deep_mirror", "emergency_mode", "recovery_writeback", "ruleops", "large_P2_features"], }, childReports, findingCount, privacy: "masked prefixes/counts/status summaries only; no phone/email/token/secret/full userid/full template_id/full sp_no/raw XML/ciphertext/form values/attachments/comments/raw payload", notes, }; fs.writeFileSync(path.join(reportDir, "summary.json"), `${JSON.stringify(summary, null, 2)}\n`, "utf8"); console.log(`[wroad2-ops] summary=${path.join(reportDir, "summary.json")}`); NODE } finish() { write_summary_json echo "[wroad2-ops] report=$REPORT_DIR" | tee -a "$SUMMARY_TXT" exit "$STATUS" } trap finish EXIT load_default_env run_shell_step "public-ready" "curl -fsS '$PUBLIC_BASE_URL/ready' >/dev/null" run_shell_finding_step "live-smoke-readiness" "QA_ISSUE_ID='WROAD2' LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' LIVE_SMOKE_READINESS_DIR='$REPORT_DIR/live-smoke-readiness' bash '$ROOT_DIR/scripts/qa/run-live-smoke-readiness.sh'" run_shell_finding_step "live-feature-flags" "LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' node '$ROOT_DIR/scripts/qa/check-live-feature-flags.mjs' --expect payment_batch_v1=force_on --expect erp_sync=force_off --expect attendance_v1=disabled --expect attendance_unified_v1=enabled --json" run_shell_step "wecom-runtime-ready" "cd '$SERVER_DIR' && npm run check:wecom-runtime-ready -- --live --format=json --report-dir='$REPORT_DIR/wecom-runtime-ready'" run_shell_step "approval-compensation-persist" "cd '$SERVER_DIR' && WECOM_ARCHIVE_WRITE=1 npm run check:wecom-approval-compensation-poc -- --live --format=json --discover-template-ids --persist-to-db --report-dir='$REPORT_DIR/wecom-approval-compensation-poc'" run_shell_step "approval-archive-offline" "cd '$SERVER_DIR' && npm run check:wecom-approval-archive -- --offline --format=json --report-dir='$REPORT_DIR/wecom-approval-archive'" run_shell_step "report-privacy-smoke" "if grep -RInE 'access_token=[A-Za-z0-9._-]{8,}|1[3-9][0-9]{9}|[[:alnum:]._%+-]+@[[:alnum:].-]+\\.[[:alpha:]]{2,}|-----BEGIN|BEGIN [A-Z ]*KEY' '$REPORT_DIR'; then exit 1; fi"