c26d6b9a73
Reviewer findings on p5-prep batch:
1. BLOCKING: prod-smoke.sh visibility grep used "id" but API items
serialize photoId (cn public.service / ov public-ov.service) — every
healthy deployment would false-FAIL release gate ④. 4 sites fixed.
2. web/web-ov healthchecks used curl on nginx:alpine (absent) → perpetual
false-unhealthy; switched to busybox wget.
3. RUNBOOK rollback rule cited nonexistent sync_outbox → outbox_events.
4. cdn-checklist invalidation sample key shape aligned to actual
variantsObjectKey layout (.../albums/{albumId}/variants/{photoId}/{version}/{variant}.jpg).
Co-Authored-By: Claude Code <noreply@anthropic.com>
187 lines
13 KiB
Bash
187 lines
13 KiB
Bash
#!/usr/bin/env bash
|
||
# scripts/prod-smoke.sh —— 生产四点冒烟(P5-prep Task 2;RUNBOOK.md §2.3 的判定口径)。
|
||
#
|
||
# 用法:
|
||
# scripts/prod-smoke.sh <BASE_CN> <BASE_OV> <ACCOUNT> <PASSWORD> [选项]
|
||
# BASE_CN/BASE_OV 两地域 API 基址(https://photo.example.cn 形态;末尾不带 /v1)
|
||
# --skip-ov ov 未部署时跳过 ov 段(--ov 为其别名,同义)
|
||
# --dry-run 只打印将执行的调用序列,不发任何请求(本地校验脚本逻辑用)
|
||
# --wait-seconds N 公开可见轮询上限秒(默认 60;worker 处理 + cn→ov 推送 2s
|
||
# 间隔通常几秒内完成,60s 覆盖慢盘/排队余量)
|
||
#
|
||
# 覆盖四点:①两地域 /v1/health ②登录(真实密码)③建时间戳相册 → presign 上传
|
||
# 真实 JPEG 字节(内置 800x600 测试图,刻意不用 1x1——避免变体流水线踩最小尺寸
|
||
# 边界)→ complete → ④公开读轮询至可见(cn)+ ov 侧同步后公开读确认。
|
||
# 结束打印 PASS/FAIL 摘要并清理(软删测试照片:公开端点即刻不可见;相册行保留
|
||
# 时间戳名供审计——相册无删除端点,照片软删后公开列表即空)。
|
||
# 依赖:bash + curl + openssl + base64(无 jq/node)。退出码:0=全过,1=有 FAIL,2=用法错。
|
||
set -u
|
||
|
||
# ── 参数 ──
|
||
BASE_CN="${1:-}"; BASE_OV="${2:-}"; ACCOUNT="${3:-}"; PASSWORD="${4:-}"
|
||
[ "$#" -ge 4 ] && shift 4
|
||
SKIP_OV=0; DRY_RUN=0; WAIT_SECONDS=60; PREV_ARG=""
|
||
for arg in "$@"; do
|
||
case "$arg" in
|
||
--skip-ov|--ov) SKIP_OV=1 ;;
|
||
--dry-run) DRY_RUN=1 ;;
|
||
--wait-seconds) : ;;
|
||
*) [ "$PREV_ARG" = "--wait-seconds" ] && WAIT_SECONDS="$arg" ;;
|
||
esac
|
||
PREV_ARG="$arg"
|
||
done
|
||
|
||
# 内置测试图:800x600 JPEG(quality 55,3118 字节)——base64 内嵌,跨机器无依赖。
|
||
JPEG_B64='/9j/2wBDAA4KCw0LCQ4NDA0QDw4RFiQXFhQUFiwgIRokNC43NjMuMjI6QVNGOj1OPjIySGJJTlZYXV5dOEVmbWVabFNbXVn/2wBDAQ8QEBYTFioXFypZOzI7WVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVn/wAARCAJYAyADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAX/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCsAkKIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//2Q=='
|
||
|
||
PASS=0; FAIL=0; FAILED_STEPS=""
|
||
TOKEN=""; ALBUM_ID=""; PUBLIC_TOKEN=""; PHOTO_ID=""; UPLOAD_ID=""; CRED_URL=""
|
||
HTTP_CODE="000"; BODY=""
|
||
JPG_FILE="$(mktemp "${TMPDIR:-/tmp}/photo-smoke-XXXXXX.jpg")"
|
||
trap cleanup EXIT
|
||
|
||
step_pass() { PASS=$((PASS + 1)); echo " [PASS] $1"; }
|
||
step_fail() { FAIL=$((FAIL + 1)); FAILED_STEPS="${FAILED_STEPS}${1}; "; echo " [FAIL] $1"; }
|
||
|
||
# json_escape <s>:处理反斜杠/双引号(密码与标题的常见特殊字符;控制字符不入参)。
|
||
json_escape() { printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'; }
|
||
|
||
# extract_str <json> <key>:取扁平响应里 "key":"value" 的 value(本脚本响应均此形态)。
|
||
extract_str() { printf '%s' "$1" | sed -n "s/.*\"$2\":\"\([^\"]*\)\".*/\1/p" | head -n 1; }
|
||
|
||
# http <method> <url> <expected-codes(逗号分隔)> [json-body]
|
||
# 成功时置 HTTP_CODE/BODY 并返回 0。dry-run 下只打印调用并返回 0。
|
||
http() {
|
||
local method="$1" url="$2" expect="$3" body="${4:-}"
|
||
if [ "$DRY_RUN" -eq 1 ]; then
|
||
echo " curl -sS -X $method '$url'${body:+ -d '$body'}${TOKEN:+ -H 'Authorization: Bearer <token>'}"
|
||
HTTP_CODE="dry"; BODY=""
|
||
return 0
|
||
fi
|
||
local tmp; tmp="$(mktemp "${TMPDIR:-/tmp}/photo-smoke-body-XXXXXX")"
|
||
local -a args=(-sS -X "$method" "$url" -H 'Content-Type: application/json' -o "$tmp" -w '%{http_code}')
|
||
[ -n "$TOKEN" ] && args+=(-H "Authorization: Bearer $TOKEN")
|
||
if [ -n "$body" ]; then
|
||
HTTP_CODE="$(curl "${args[@]}" -d "$body" 2>/dev/null || echo 000)"
|
||
else
|
||
HTTP_CODE="$(curl "${args[@]}" 2>/dev/null || echo 000)"
|
||
fi
|
||
BODY="$(cat "$tmp" 2>/dev/null || true)"
|
||
rm -f "$tmp"
|
||
case ",$expect," in
|
||
*",$HTTP_CODE,"*) return 0 ;;
|
||
*) return 1 ;;
|
||
esac
|
||
}
|
||
|
||
cleanup() {
|
||
if [ "$DRY_RUN" -eq 0 ] && [ -n "$PHOTO_ID" ] && [ -n "$TOKEN" ] && [ "$TOKEN" != "dry" ]; then
|
||
echo "-- 清理:软删测试照片 $PHOTO_ID(公开列表即刻为空)"
|
||
if http DELETE "$BASE_CN/v1/photos/$PHOTO_ID" 204; then step_pass "cleanup 软删照片"; else step_fail "cleanup 软删照片 (HTTP=$HTTP_CODE)"; fi
|
||
fi
|
||
rm -f "$JPG_FILE"
|
||
}
|
||
|
||
echo "== photo-live 生产冒烟 =="
|
||
echo " cn=$BASE_CN ov=$BASE_OV account=$ACCOUNT skip_ov=$SKIP_OV dry_run=$DRY_RUN"
|
||
if [ -z "$BASE_CN" ] || [ -z "$ACCOUNT" ] || [ -z "$PASSWORD" ] || { [ "$SKIP_OV" -eq 0 ] && [ -z "$BASE_OV" ]; }; then
|
||
echo "用法: $0 <BASE_CN> <BASE_OV> <ACCOUNT> <PASSWORD> [--skip-ov|--ov] [--dry-run] [--wait-seconds N]" >&2
|
||
exit 2
|
||
fi
|
||
printf '%s' "$JPEG_B64" | base64 -d >"$JPG_FILE" 2>/dev/null || { echo "base64 解码失败(需 GNU base64)" >&2; exit 2; }
|
||
JPEG_BYTES=$(wc -c <"$JPG_FILE" | tr -d ' ')
|
||
TS=$(date +%Y%m%d%H%M%S)
|
||
|
||
echo "-- ① 健康检查(两地域)"
|
||
if http GET "$BASE_CN/v1/health" 200; then step_pass "cn /v1/health"; else step_fail "cn /v1/health (HTTP=$HTTP_CODE)"; fi
|
||
if [ "$SKIP_OV" -eq 0 ]; then
|
||
if http GET "$BASE_OV/v1/health" 200; then step_pass "ov /v1/health"; else step_fail "ov /v1/health (HTTP=$HTTP_CODE)"; fi
|
||
fi
|
||
|
||
echo "-- ② 登录(真实密码,会话走 Bearer)"
|
||
if [ "$DRY_RUN" -eq 1 ]; then
|
||
http POST "$BASE_CN/v1/auth/password" 200 \
|
||
"{\"account\":\"$(json_escape "$ACCOUNT")\",\"password\":\"$(json_escape "$PASSWORD")\"}"
|
||
TOKEN="<dry-run-token>"
|
||
else
|
||
if http POST "$BASE_CN/v1/auth/password" 200 \
|
||
"{\"account\":\"$(json_escape "$ACCOUNT")\",\"password\":\"$(json_escape "$PASSWORD")\"}"; then
|
||
TOKEN=$(extract_str "$BODY" sessionToken)
|
||
fi
|
||
fi
|
||
if [ -n "$TOKEN" ]; then
|
||
step_pass "登录(sessionToken 获取)"
|
||
else
|
||
step_fail "登录 (HTTP=$HTTP_CODE BODY=$BODY)"
|
||
echo "== 摘要:PASS=$PASS FAIL=$FAIL 失败项: $FAILED_STEPS =="; echo "RESULT: FAIL"
|
||
exit 1
|
||
fi
|
||
|
||
echo "-- ③ 建时间戳相册 → presign 上传 → complete"
|
||
if http POST "$BASE_CN/v1/albums" 201 \
|
||
"{\"translations\":[{\"locale\":\"zh-CN\",\"title\":\"smoke-$TS\",\"description\":\"prod-smoke 自动冒烟\"}],\"defaultLocale\":\"zh-CN\",\"startAt\":\"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\"}"; then
|
||
ALBUM_ID=$(extract_str "$BODY" id); PUBLIC_TOKEN=$(extract_str "$BODY" publicToken)
|
||
fi
|
||
if [ -n "$ALBUM_ID" ] && [ -n "$PUBLIC_TOKEN" ]; then
|
||
step_pass "建相册(publicToken=$PUBLIC_TOKEN)"
|
||
elif [ "$DRY_RUN" -eq 1 ]; then
|
||
ALBUM_ID="<albumId>"; PUBLIC_TOKEN="<publicToken>"; step_pass "建相册(dry-run 占位)"
|
||
else
|
||
step_fail "建相册 (HTTP=$HTTP_CODE BODY=$BODY)"
|
||
fi
|
||
|
||
if http POST "$BASE_CN/v1/uploads" 201 \
|
||
"{\"albumId\":\"$ALBUM_ID\",\"clientTaskId\":\"smoke-$TS\",\"filename\":\"smoke.jpg\",\"bytes\":$JPEG_BYTES,\"contentType\":\"image/jpeg\"}"; then
|
||
UPLOAD_ID=$(extract_str "$BODY" uploadId); PHOTO_ID=$(extract_str "$BODY" photoId); CRED_URL=$(extract_str "$BODY" url)
|
||
fi
|
||
if [ -n "$UPLOAD_ID" ] && [ -n "$CRED_URL" ]; then
|
||
step_pass "presign 凭证(uploadId=$UPLOAD_ID)"
|
||
elif [ "$DRY_RUN" -eq 1 ]; then
|
||
UPLOAD_ID="<uploadId>"; PHOTO_ID="<photoId>"; CRED_URL="<presigned-url>"; step_pass "presign 凭证(dry-run 占位)"
|
||
else
|
||
step_fail "presign (HTTP=$HTTP_CODE BODY=$BODY)"
|
||
fi
|
||
|
||
if [ "$DRY_RUN" -eq 1 ]; then
|
||
echo " curl -sS -X PUT '$CRED_URL' -H 'Content-Type: image/jpeg' --data-binary @$JPG_FILE ($JPEG_BYTES 字节)"
|
||
echo " (complete → cn/ov 公开读轮询逻辑与真实流程一致,见脚本 --dry-run 外执行)"
|
||
else
|
||
PUT_CODE="$(curl -sS -X PUT "$CRED_URL" -H 'Content-Type: image/jpeg' --data-binary @"$JPG_FILE" -o /dev/null -w '%{http_code}' 2>/dev/null || echo 000)"
|
||
case "$PUT_CODE" in
|
||
2*) step_pass "OSS presign PUT($JPEG_BYTES 字节真实上传)" ;;
|
||
*) step_fail "OSS PUT (HTTP=$PUT_CODE)" ;;
|
||
esac
|
||
if http POST "$BASE_CN/v1/uploads/$UPLOAD_ID/complete" 200; then step_pass "complete(进入 worker 处理)"; else step_fail "complete (HTTP=$HTTP_CODE BODY=$BODY)"; fi
|
||
fi
|
||
|
||
echo "-- ④ 公开读轮询至可见(≤${WAIT_SECONDS}s;worker 变体流水线 + 发布事务为异步)"
|
||
if [ "$DRY_RUN" -eq 1 ]; then
|
||
echo " 轮询 GET $BASE_CN/v1/public/albums/$PUBLIC_TOKEN/photos(间隔 2s)直到响应含 \"photoId\":\"$PHOTO_ID\""
|
||
else
|
||
SEEN_CN=0; WAITED=0
|
||
while [ "$WAITED" -le "$WAIT_SECONDS" ]; do
|
||
http GET "$BASE_CN/v1/public/albums/$PUBLIC_TOKEN/photos" 200 || true
|
||
if [ -n "$PHOTO_ID" ] && printf '%s' "$BODY" | grep -q "\"photoId\":\"$PHOTO_ID\""; then SEEN_CN=1; break; fi
|
||
sleep 2; WAITED=$((WAITED + 2))
|
||
done
|
||
if [ "$SEEN_CN" -eq 1 ]; then step_pass "cn 公开可见(${WAITED}s)"; else step_fail "cn 公开可见超时 ${WAIT_SECONDS}s(查 worker 日志)"; fi
|
||
fi
|
||
|
||
if [ "$SKIP_OV" -eq 0 ]; then
|
||
echo "-- ④' ov 侧公开读确认(cn→ov 同步:推送 2s 间隔 + 对账 5min 兜底)"
|
||
if [ "$DRY_RUN" -eq 1 ]; then
|
||
echo " 轮询 GET $BASE_OV/v1/public/albums/$PUBLIC_TOKEN/photos(间隔 2s)直到响应含 \"photoId\":\"$PHOTO_ID\""
|
||
else
|
||
SEEN_OV=0; WAITED=0
|
||
while [ "$WAITED" -le "$WAIT_SECONDS" ]; do
|
||
http GET "$BASE_OV/v1/public/albums/$PUBLIC_TOKEN/photos" 200 || true
|
||
if [ -n "$PHOTO_ID" ] && printf '%s' "$BODY" | grep -q "\"photoId\":\"$PHOTO_ID\""; then SEEN_OV=1; break; fi
|
||
sleep 2; WAITED=$((WAITED + 2))
|
||
done
|
||
if [ "$SEEN_OV" -eq 1 ]; then step_pass "ov 公开可见(同步链路端到端,${WAITED}s)"; else step_fail "ov 公开可见超时 ${WAIT_SECONDS}s(查 sync 日志 / RUNBOOK §5.1)"; fi
|
||
fi
|
||
fi
|
||
|
||
echo "== 摘要:PASS=$PASS FAIL=$FAIL ${FAILED_STEPS:+失败项: $FAILED_STEPS}=="
|
||
if [ "$FAIL" -eq 0 ]; then echo "RESULT: PASS"; else echo "RESULT: FAIL"; exit 1; fi
|