#!/usr/bin/env bash set -u -o pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" CLIENT_DIR="$ROOT_DIR/client" PUBLIC_BASE_URL="${LIVE_SMOKE_BASE_URL:-https://oa.hyinda.com}" REPORT_DIR="${LIVE_SMOKE_READINESS_DIR:-$ROOT_DIR/artifacts/qa-flow/live-smoke-readiness-$(date +%Y%m%d-%H%M%S)}" REPORT_TOOL="$ROOT_DIR/scripts/qa/live-smoke-readiness-report.mjs" STATUS=0 SOURCE_COMMIT="$(git -C "$ROOT_DIR" rev-parse HEAD 2>/dev/null || printf 'unknown')" SOURCE_SHORT_COMMIT="${SOURCE_COMMIT:0:12}" EXPECTED_BUILD_SHA="${LIVE_SMOKE_EXPECTED_BUILD_SHA:-$SOURCE_SHORT_COMMIT}" FORBIDDEN_FULL_BUILD_SHA="" if [[ "$SOURCE_COMMIT" =~ ^[0-9a-fA-F]{40,64}$ && "${EXPECTED_BUILD_SHA:0:12}" == "$SOURCE_SHORT_COMMIT" ]]; then FORBIDDEN_FULL_BUILD_SHA="$SOURCE_COMMIT" fi mkdir -p "$REPORT_DIR" write_step() { local name="$1" local status="$2" local kind="$3" local code="$4" local finding_count="$5" local detail="$6" local artifact_file="${7:-}" node "$REPORT_TOOL" write-step \ "$ROOT_DIR" "$REPORT_DIR" "$name" "$status" "$kind" "$code" "$finding_count" "$detail" "$artifact_file" } apply_step_status() { local status="$1" local kind="$2" if [[ "$kind" == "required" && "$status" =~ FAIL|ERROR|BLOCKED ]]; then STATUS=1 fi } run_command_step() { local name="$1" local kind="$2" local detail="$3" shift 3 local log_file="$REPORT_DIR/${name}.log" local code=0 local status="PASS" local finding_count=0 echo ">>> [live-readiness] $name" if "$@" > >(tee "$log_file") 2>&1; then echo "[live-readiness][PASS] $name" else code="$?" status="FAIL" finding_count=1 echo "[live-readiness][FAIL] $name exit=$code" fi write_step "$name" "$status" "$kind" "$code" "$finding_count" "$detail" "$log_file" apply_step_status "$status" "$kind" } run_json_step() { local name="$1" local kind="$2" local detail="$3" local artifact_file="$4" shift 4 local log_file="$REPORT_DIR/${name}.log" local code=0 echo ">>> [live-readiness] $name" if "$@" >"$artifact_file" 2> >(tee "$log_file" >&2); then code=0 else code="$?" fi local parsed parsed="$(node -e ' const fs = require("node:fs"); try { const value = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); const status = String(value.status || "FAIL").toUpperCase(); const count = Number(value.findingCount || 0); process.stdout.write(`${status}\t${Number.isFinite(count) ? count : 1}`); } catch { process.stdout.write("FAIL\t1"); process.exitCode = 1; } ' "$artifact_file")" || true local status="FAIL" local finding_count=1 IFS=$'\t' read -r status finding_count <<<"$parsed" if [[ "$code" -ne 0 && ! "$status" =~ FAIL|ERROR|BLOCKED ]]; then status="FAIL" finding_count=1 fi echo "[live-readiness][$status] $name findings=$finding_count" write_step "$name" "$status" "$kind" "$code" "$finding_count" "$detail" "$artifact_file" apply_step_status "$status" "$kind" } write_skip_step() { local name="$1" local detail="$2" local log_file="$REPORT_DIR/${name}.log" echo "[live-readiness][SKIP] $name:$detail" | tee "$log_file" write_step "$name" "SKIP" "finding_allowed" 0 1 "$detail" "$log_file" } has_live_smoke_credentials() { [[ -n "${LIVE_SMOKE_ADMIN_IDENTIFIER:-}" ]] && [[ -n "${LIVE_SMOKE_ADMIN_PASSWORD:-}" ]] && [[ -n "${LIVE_SMOKE_EMPLOYEE_IDENTIFIER:-}" ]] && [[ -n "${LIVE_SMOKE_EMPLOYEE_PASSWORD:-}" ]] } has_live_ocr_credentials() { if [[ -n "${PLAYWRIGHT_OCR_LIVE_IDENTIFIER:-}" && -n "${PLAYWRIGHT_OCR_LIVE_PASSWORD:-}" ]]; then return 0 fi [[ -n "${LIVE_SMOKE_EMPLOYEE_IDENTIFIER:-}" ]] && [[ -n "${LIVE_SMOKE_EMPLOYEE_PASSWORD:-}" ]] } has_live_ocr_sample() { if [[ -n "${PLAYWRIGHT_OCR_LIVE_FILE:-}" && -f "$PLAYWRIGHT_OCR_LIVE_FILE" ]]; then return 0 fi if [[ -n "${LIVE_OCR_INVOICE_FILE:-}" && -f "$LIVE_OCR_INVOICE_FILE" ]]; then export PLAYWRIGHT_OCR_LIVE_FILE="$LIVE_OCR_INVOICE_FILE" return 0 fi if [[ -n "${PLAYWRIGHT_OCR_LIVE_DIR:-}" && -d "$PLAYWRIGHT_OCR_LIVE_DIR" ]]; then return 0 fi return 1 } run_json_step \ "environment" \ "required" \ "public endpoints and safe readiness labels" \ "$REPORT_DIR/environment.json" \ env LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" LIVE_SMOKE_EXPECTED_BUILD_SHA="$EXPECTED_BUILD_SHA" node "$ROOT_DIR/scripts/qa/check-live-smoke-env.mjs" --json run_command_step \ "publicReady" \ "required" \ "public readiness endpoint" \ curl -fsS "$PUBLIC_BASE_URL/ready" run_command_step \ "publicBundle" \ "required" \ "public bundle copy and build marker scan" \ env PUBLIC_EXPECTED_BUILD_SHA="$EXPECTED_BUILD_SHA" PUBLIC_FORBIDDEN_BUILD_SHA="$FORBIDDEN_FULL_BUILD_SHA" node "$ROOT_DIR/scripts/qa/check-public-bundle-copy.mjs" "$PUBLIC_BASE_URL" run_json_step \ "pendingOcr" \ "required" \ "pending OCR count summary" \ "$REPORT_DIR/pending-ocr.json" \ node "$ROOT_DIR/scripts/qa/check-pending-ocr.mjs" --thresholds=2,5,10 --json if has_live_smoke_credentials; then run_command_step \ "liveSmoke" \ "required" \ "authenticated public smoke" \ env LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" bash "$ROOT_DIR/scripts/qa/run-web-gates.sh" smoke-live else write_skip_step "liveSmoke" "authenticated smoke prerequisites are incomplete" fi if has_live_ocr_credentials && has_live_ocr_sample; then run_command_step \ "liveOcr" \ "required" \ "live OCR sample verification" \ bash -c 'cd "$1" && PLAYWRIGHT_MANUAL=1 PLAYWRIGHT_OCR_LIVE=1 PLAYWRIGHT_OCR_ALLOW_DUPLICATE="${PLAYWRIGHT_OCR_ALLOW_DUPLICATE:-1}" E2E_BASE_URL="$2" npx playwright test tests/e2e/manual/live-invoice-ocr.spec.ts --workers=1 --reporter=list' _ "$CLIENT_DIR" "$PUBLIC_BASE_URL" else write_skip_step "liveOcr" "OCR credentials or PDF sample are incomplete" fi if node "$REPORT_TOOL" privacy "$ROOT_DIR" "$REPORT_DIR" >"$REPORT_DIR/privacy.log" 2>&1; then write_step "privacy" "PASS" "required" 0 0 "readiness report privacy scan" "$REPORT_DIR/privacy-findings.json" else write_step "privacy" "FAIL" "required" 1 1 "redacted privacy findings detected" "$REPORT_DIR/privacy-findings.json" STATUS=1 fi if ! node "$REPORT_TOOL" summarize "$ROOT_DIR" "$REPORT_DIR" "$PUBLIC_BASE_URL"; then STATUS=1 fi summary_status="$(node -e 'const s=require(process.argv[1]); process.stdout.write(String(s.status || "UNKNOWN"))' "$REPORT_DIR/summary.json" 2>/dev/null || printf 'UNKNOWN')" summary_ref="$(node -e 'const s=require(process.argv[1]); process.stdout.write(String(s.reportRef || "summary unavailable"))' "$REPORT_DIR/summary.json" 2>/dev/null || printf 'summary unavailable')" echo "[live-readiness][$summary_status] readiness 检查完成" echo "[live-readiness] report=$summary_ref" exit "$STATUS"