恢复付款入口与常开基线(08-01-reimbursement-notification-progress-clarity)
这个提交包含在:
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { chmod, readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
const DEFAULT_BASE_URL = "https://oa.hyinda.com";
|
||||
const FLAG_KEY = "payment_batch_v1";
|
||||
|
||||
function parseArgs(argv) {
|
||||
const [action, ...rest] = argv;
|
||||
const options = {
|
||||
action,
|
||||
baseUrl: process.env.LIVE_SMOKE_BASE_URL || DEFAULT_BASE_URL,
|
||||
snapshotFile: "",
|
||||
};
|
||||
|
||||
for (let index = 0; index < rest.length; index += 1) {
|
||||
const arg = rest[index];
|
||||
if (arg === "--snapshot-file") {
|
||||
options.snapshotFile = rest[index + 1] ?? "";
|
||||
index += 1;
|
||||
} else if (arg.startsWith("--snapshot-file=")) {
|
||||
options.snapshotFile = arg.slice("--snapshot-file=".length);
|
||||
} else if (arg.startsWith("--base-url=")) {
|
||||
options.baseUrl = arg.slice("--base-url=".length);
|
||||
}
|
||||
}
|
||||
|
||||
if (!new Set(["capture", "restore"]).has(options.action)) {
|
||||
throw new Error("action must be capture or restore");
|
||||
}
|
||||
if (!options.snapshotFile.trim()) {
|
||||
throw new Error("--snapshot-file is required");
|
||||
}
|
||||
options.baseUrl = options.baseUrl.trim().replace(/\/$/, "");
|
||||
options.snapshotFile = options.snapshotFile.trim();
|
||||
return options;
|
||||
}
|
||||
|
||||
function getAdminCredential() {
|
||||
const identifier = process.env.LIVE_SMOKE_ADMIN_IDENTIFIER?.trim();
|
||||
const password = process.env.LIVE_SMOKE_ADMIN_PASSWORD?.trim();
|
||||
if (!identifier || !password) {
|
||||
throw new Error("missing LIVE_SMOKE_ADMIN_* credentials");
|
||||
}
|
||||
return { identifier, password };
|
||||
}
|
||||
|
||||
async function fetchJson(url, init = {}) {
|
||||
const response = await fetch(url, {
|
||||
...init,
|
||||
signal: AbortSignal.timeout(20_000),
|
||||
});
|
||||
const text = await response.text();
|
||||
let json = null;
|
||||
try {
|
||||
json = text ? JSON.parse(text) : null;
|
||||
} catch {
|
||||
json = null;
|
||||
}
|
||||
return { ok: response.ok, status: response.status, json };
|
||||
}
|
||||
|
||||
function errorCode(json) {
|
||||
return typeof json?.error?.code === "string" ? json.error.code : "";
|
||||
}
|
||||
|
||||
async function login(baseUrl, credential) {
|
||||
const result = await fetchJson(`${baseUrl}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-device-fingerprint": "payment-batch-flag-window-snapshot",
|
||||
},
|
||||
body: JSON.stringify(credential),
|
||||
});
|
||||
const accessToken = result.json?.data?.accessToken;
|
||||
if (!result.ok || typeof accessToken !== "string" || !accessToken) {
|
||||
throw new Error(`admin login failed: HTTP ${result.status} ${errorCode(result.json)}`.trim());
|
||||
}
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
async function loadFlag(baseUrl, accessToken) {
|
||||
const result = await fetchJson(`${baseUrl}/api/system/feature-flags`, {
|
||||
headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new Error(`feature flag list failed: HTTP ${result.status} ${errorCode(result.json)}`.trim());
|
||||
}
|
||||
const flag = result.json?.data?.items?.find((item) => item.flagKey === FLAG_KEY);
|
||||
if (!flag) {
|
||||
throw new Error(`missing feature flag: ${FLAG_KEY}`);
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
function toSnapshot(flag) {
|
||||
return {
|
||||
flagKey: FLAG_KEY,
|
||||
overrideMode: flag.overrideMode,
|
||||
rolloutType: flag.rolloutType,
|
||||
departmentIds: Array.isArray(flag.departmentIds) ? [...flag.departmentIds] : [],
|
||||
note: typeof flag.note === "string" ? flag.note : "",
|
||||
effectiveEnabled: flag.effectiveEnabled === true,
|
||||
effectiveReason: typeof flag.effectiveReason === "string" ? flag.effectiveReason : "unknown",
|
||||
};
|
||||
}
|
||||
|
||||
function validateSnapshot(value) {
|
||||
const overrideModes = new Set(["inherit", "force_on", "force_off"]);
|
||||
const rolloutTypes = new Set(["global", "department_allowlist"]);
|
||||
if (
|
||||
value?.flagKey !== FLAG_KEY ||
|
||||
!overrideModes.has(value.overrideMode) ||
|
||||
!rolloutTypes.has(value.rolloutType) ||
|
||||
!Array.isArray(value.departmentIds) ||
|
||||
!value.departmentIds.every((item) => typeof item === "string") ||
|
||||
typeof value.note !== "string" ||
|
||||
typeof value.effectiveEnabled !== "boolean" ||
|
||||
typeof value.effectiveReason !== "string"
|
||||
) {
|
||||
throw new Error("invalid payment batch flag snapshot");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function sameConfiguration(flag, snapshot) {
|
||||
return flag.overrideMode === snapshot.overrideMode &&
|
||||
flag.rolloutType === snapshot.rolloutType &&
|
||||
JSON.stringify(flag.departmentIds ?? []) === JSON.stringify(snapshot.departmentIds) &&
|
||||
(flag.note ?? "") === snapshot.note;
|
||||
}
|
||||
|
||||
async function updateFlag(baseUrl, accessToken, current, snapshot) {
|
||||
const result = await fetchJson(`${baseUrl}/api/system/feature-flags/${FLAG_KEY}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
authorization: `Bearer ${accessToken}`,
|
||||
"content-type": "application/json",
|
||||
accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
overrideMode: snapshot.overrideMode,
|
||||
rolloutType: snapshot.rolloutType,
|
||||
departmentIds: snapshot.departmentIds,
|
||||
note: snapshot.note,
|
||||
expectedVersion: current.version,
|
||||
}),
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new Error(`feature flag restore failed: HTTP ${result.status} ${errorCode(result.json)}`.trim());
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (process.env.P2_FIN_LIVE_APPROVED !== "1") {
|
||||
throw new Error("P2_FIN_LIVE_APPROVED=1 is required");
|
||||
}
|
||||
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
const credential = getAdminCredential();
|
||||
const accessToken = await login(options.baseUrl, credential);
|
||||
|
||||
if (options.action === "capture") {
|
||||
const current = await loadFlag(options.baseUrl, accessToken);
|
||||
await writeFile(options.snapshotFile, `${JSON.stringify(toSnapshot(current), null, 2)}\n`, { mode: 0o600 });
|
||||
await chmod(options.snapshotFile, 0o600);
|
||||
console.log(`[payment-flag-snapshot] captured override=${current.overrideMode} effective=${current.effectiveEnabled}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = validateSnapshot(JSON.parse(await readFile(options.snapshotFile, "utf8")));
|
||||
const current = await loadFlag(options.baseUrl, accessToken);
|
||||
if (!sameConfiguration(current, snapshot)) {
|
||||
await updateFlag(options.baseUrl, accessToken, current, snapshot);
|
||||
}
|
||||
const restored = await loadFlag(options.baseUrl, accessToken);
|
||||
if (!sameConfiguration(restored, snapshot)) {
|
||||
throw new Error("payment batch flag does not match caller snapshot after restore");
|
||||
}
|
||||
console.log(`[payment-flag-snapshot] restored override=${restored.overrideMode} effective=${restored.effectiveEnabled}`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`[payment-flag-snapshot][ERROR] ${error instanceof Error ? error.message : String(error)}`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -6,6 +6,7 @@ CLIENT_DIR="$ROOT_DIR/client"
|
||||
PUBLIC_BASE_URL="${LIVE_SMOKE_BASE_URL:-https://oa.hyinda.com}"
|
||||
REPORT_DIR="${FIN_PROD_ENABLE_REPORT_DIR:-$ROOT_DIR/artifacts/qa-flow/fin-prod-enable-$(date +%Y%m%d-%H%M%S)}"
|
||||
SUMMARY_TXT="$REPORT_DIR/summary.txt"
|
||||
FLAG_SNAPSHOT_FILE="$REPORT_DIR/payment-batch-flag-before.json"
|
||||
STATUS=0
|
||||
|
||||
mkdir -p "$REPORT_DIR"
|
||||
@@ -124,101 +125,18 @@ if (expected === "force_off" && (!paymentOff || !erpOff)) {
|
||||
NODE
|
||||
}
|
||||
|
||||
force_payment_batch_flag_off() {
|
||||
node <<'NODE'
|
||||
const baseUrl = (process.env.LIVE_SMOKE_BASE_URL || "https://oa.hyinda.com").replace(/\/$/, "");
|
||||
const identifier = process.env.LIVE_SMOKE_ADMIN_IDENTIFIER?.trim();
|
||||
const password = process.env.LIVE_SMOKE_ADMIN_PASSWORD?.trim();
|
||||
const issueId = process.env.QA_ISSUE_ID?.trim() || "FINPROD";
|
||||
|
||||
if (!identifier || !password) {
|
||||
console.error("[fin-prod-enable][rollback] missing admin credentials");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function fetchJson(url, init = {}) {
|
||||
const response = await fetch(url, {
|
||||
...init,
|
||||
signal: AbortSignal.timeout(20_000),
|
||||
});
|
||||
const text = await response.text();
|
||||
let json = null;
|
||||
try {
|
||||
json = text ? JSON.parse(text) : null;
|
||||
} catch {
|
||||
json = null;
|
||||
}
|
||||
return { response, json };
|
||||
}
|
||||
|
||||
function errorSummary(json, status) {
|
||||
const error = json?.error ?? {};
|
||||
return `${status} ${error.code ?? ""} ${error.message ?? ""}`.trim();
|
||||
}
|
||||
|
||||
const login = await fetchJson(`${baseUrl}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-device-fingerprint": "fin-prod-enable-rollback",
|
||||
},
|
||||
body: JSON.stringify({ identifier, password }),
|
||||
});
|
||||
if (!login.response.ok || !login.json?.data?.accessToken) {
|
||||
console.error(`[fin-prod-enable][rollback] login failed: ${errorSummary(login.json, login.response.status)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const token = login.json.data.accessToken;
|
||||
const list = await fetchJson(`${baseUrl}/api/system/feature-flags`, {
|
||||
headers: { authorization: `Bearer ${token}`, accept: "application/json" },
|
||||
});
|
||||
if (!list.response.ok) {
|
||||
console.error(`[fin-prod-enable][rollback] list flags failed: ${errorSummary(list.json, list.response.status)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const flag = list.json?.data?.items?.find((item) => item.flagKey === "payment_batch_v1");
|
||||
if (!flag) {
|
||||
console.error("[fin-prod-enable][rollback] missing payment_batch_v1");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (flag.overrideMode === "force_off" && flag.effectiveEnabled === false) {
|
||||
console.log("[fin-prod-enable][rollback] payment_batch_v1 already force_off");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const updated = await fetchJson(`${baseUrl}/api/system/feature-flags/payment_batch_v1`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
"content-type": "application/json",
|
||||
accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
overrideMode: "force_off",
|
||||
rolloutType: "global",
|
||||
departmentIds: [],
|
||||
note: `${issueId} failed final rollback`,
|
||||
expectedVersion: flag.version,
|
||||
}),
|
||||
});
|
||||
if (!updated.response.ok) {
|
||||
console.error(`[fin-prod-enable][rollback] update failed: ${errorSummary(updated.json, updated.response.status)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const item = updated.json?.data?.item;
|
||||
console.log(`[fin-prod-enable][rollback] payment_batch_v1 override=${item?.overrideMode ?? "unknown"} effective=${item?.effectiveEnabled ?? "unknown"}`);
|
||||
NODE
|
||||
restore_payment_batch_flag_snapshot() {
|
||||
P2_FIN_LIVE_APPROVED=1 LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" \
|
||||
node "$ROOT_DIR/scripts/qa/payment-batch-flag-snapshot.mjs" restore \
|
||||
--snapshot-file "$FLAG_SNAPSHOT_FILE"
|
||||
}
|
||||
|
||||
write_summary_json() {
|
||||
local qa_status="PASS"
|
||||
local notes="FINPROD 正式启用:公网付款批次验收通过后保持 payment_batch_v1=force_on;失败时回滚 force_off;不接真实支付、不接 ERP、不开放图片 OCR。"
|
||||
local notes="FINPROD 正式启用:公网付款批次验收通过后保持 payment_batch_v1=force_on;失败时恢复调用前完整快照;不接真实支付、不接 ERP、不开放图片 OCR。"
|
||||
if [[ "$STATUS" -ne 0 ]]; then
|
||||
qa_status="FAIL"
|
||||
notes="FINPROD 正式启用失败或门禁失败:已尝试将 payment_batch_v1 回滚到 force_off;不接真实支付、不接 ERP、不开放图片 OCR。"
|
||||
notes="FINPROD 正式启用失败或门禁失败:已尝试恢复 payment_batch_v1 调用前完整快照;不接真实支付、不接 ERP、不开放图片 OCR。"
|
||||
fi
|
||||
|
||||
QA_REPORT_DIR="$REPORT_DIR" \
|
||||
@@ -243,21 +161,13 @@ finish() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$STATUS" -ne 0 ]]; then
|
||||
echo ">>> [fin-prod-enable] failure-force-flag-off" | tee -a "$SUMMARY_TXT"
|
||||
if force_payment_batch_flag_off > >(tee "$REPORT_DIR/failure-force-flag-off.log") 2>&1; then
|
||||
echo "[fin-prod-enable][PASS] failure-force-flag-off" | tee -a "$SUMMARY_TXT"
|
||||
if [[ "$STATUS" -ne 0 && -f "$FLAG_SNAPSHOT_FILE" ]]; then
|
||||
echo ">>> [fin-prod-enable] restore-caller-flag-snapshot" | tee -a "$SUMMARY_TXT"
|
||||
if restore_payment_batch_flag_snapshot > >(tee "$REPORT_DIR/restore-caller-flag-snapshot.log") 2>&1; then
|
||||
echo "[fin-prod-enable][PASS] restore-caller-flag-snapshot" | tee -a "$SUMMARY_TXT"
|
||||
else
|
||||
local code="$?"
|
||||
echo "[fin-prod-enable][FAIL] failure-force-flag-off exit=$code" | tee -a "$SUMMARY_TXT"
|
||||
fi
|
||||
|
||||
echo ">>> [fin-prod-enable] final-flag-off-check" | tee -a "$SUMMARY_TXT"
|
||||
if LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" fetch_feature_flags_node "force_off" > >(tee "$REPORT_DIR/final-flag-off-check.log") 2>&1; then
|
||||
echo "[fin-prod-enable][PASS] final-flag-off-check" | tee -a "$SUMMARY_TXT"
|
||||
else
|
||||
local code="$?"
|
||||
echo "[fin-prod-enable][FAIL] final-flag-off-check exit=$code" | tee -a "$SUMMARY_TXT"
|
||||
echo "[fin-prod-enable][FAIL] restore-caller-flag-snapshot exit=$code" | tee -a "$SUMMARY_TXT"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -283,6 +193,9 @@ run_shell_step "live-env-strict" "LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' node '$
|
||||
run_shell_step "pre-flag-off-check" "LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' node '$ROOT_DIR/scripts/qa/check-payment-batch-flag-off.mjs' --json"
|
||||
[[ "$STATUS" -eq 0 ]] || exit "$STATUS"
|
||||
|
||||
run_shell_step "capture-caller-flag-snapshot" "P2_FIN_LIVE_APPROVED=1 LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' node '$ROOT_DIR/scripts/qa/payment-batch-flag-snapshot.mjs' capture --snapshot-file '$FLAG_SNAPSHOT_FILE'"
|
||||
[[ "$STATUS" -eq 0 ]] || exit "$STATUS"
|
||||
|
||||
run_shell_step "fin-prod-enable-spec" "cd '$CLIENT_DIR' && PLAYWRIGHT_MANUAL=1 E2E_MODE=live P2_FIN_LIVE_APPROVED=1 P2_FIN_LIVE_FINAL_STATE=force_on QA_ISSUE_ID='${QA_ISSUE_ID:-FINPROD}' P2_FIN_LIVE_MARKER_PREFIX='${P2_FIN_LIVE_MARKER_PREFIX:-${QA_ISSUE_ID:-FINPROD}}' P2_FIN_LIVE_REPORT_DIR='$REPORT_DIR' LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' npx playwright test tests/e2e/manual/p2-fin-live-payment-batch.spec.ts --workers=1 --reporter=list"
|
||||
|
||||
if [[ "${FIN_PROD_ENABLE_SKIP_MOBILE:-0}" == "1" ]]; then
|
||||
|
||||
+19
-104
@@ -6,7 +6,9 @@ CLIENT_DIR="$ROOT_DIR/client"
|
||||
PUBLIC_BASE_URL="${LIVE_SMOKE_BASE_URL:-https://oa.hyinda.com}"
|
||||
REPORT_DIR="${P2_FIN_LIVE_REPORT_DIR:-$ROOT_DIR/artifacts/qa-flow/p2-fin-live-$(date +%Y%m%d-%H%M%S)}"
|
||||
SUMMARY_TXT="$REPORT_DIR/summary.txt"
|
||||
FLAG_SNAPSHOT_FILE="$REPORT_DIR/payment-batch-flag-before.json"
|
||||
STATUS=0
|
||||
ALREADY_ON_MODE=0
|
||||
|
||||
mkdir -p "$REPORT_DIR"
|
||||
: >"$SUMMARY_TXT"
|
||||
@@ -46,93 +48,10 @@ run_shell_step() {
|
||||
run_step "$name" bash -lc "$command"
|
||||
}
|
||||
|
||||
force_payment_batch_flag_off() {
|
||||
node <<'NODE'
|
||||
const baseUrl = (process.env.LIVE_SMOKE_BASE_URL || "https://oa.hyinda.com").replace(/\/$/, "");
|
||||
const identifier = process.env.LIVE_SMOKE_ADMIN_IDENTIFIER?.trim();
|
||||
const password = process.env.LIVE_SMOKE_ADMIN_PASSWORD?.trim();
|
||||
const issueId = process.env.QA_ISSUE_ID?.trim() || "P2FLIVE";
|
||||
|
||||
if (!identifier || !password) {
|
||||
console.error("[p2-fin-live][rollback] missing admin credentials");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function fetchJson(url, init = {}) {
|
||||
const response = await fetch(url, {
|
||||
...init,
|
||||
signal: AbortSignal.timeout(20_000),
|
||||
});
|
||||
const text = await response.text();
|
||||
let json = null;
|
||||
try {
|
||||
json = text ? JSON.parse(text) : null;
|
||||
} catch {
|
||||
json = null;
|
||||
}
|
||||
return { response, json };
|
||||
}
|
||||
|
||||
function errorSummary(json, status) {
|
||||
const error = json?.error ?? {};
|
||||
return `${status} ${error.code ?? ""} ${error.message ?? ""}`.trim();
|
||||
}
|
||||
|
||||
const login = await fetchJson(`${baseUrl}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-device-fingerprint": "p2-fin-live-runner-rollback",
|
||||
},
|
||||
body: JSON.stringify({ identifier, password }),
|
||||
});
|
||||
if (!login.response.ok || !login.json?.data?.accessToken) {
|
||||
console.error(`[p2-fin-live][rollback] login failed: ${errorSummary(login.json, login.response.status)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const token = login.json.data.accessToken;
|
||||
const list = await fetchJson(`${baseUrl}/api/system/feature-flags`, {
|
||||
headers: { authorization: `Bearer ${token}`, accept: "application/json" },
|
||||
});
|
||||
if (!list.response.ok) {
|
||||
console.error(`[p2-fin-live][rollback] list flags failed: ${errorSummary(list.json, list.response.status)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const flag = list.json?.data?.items?.find((item) => item.flagKey === "payment_batch_v1");
|
||||
if (!flag) {
|
||||
console.error("[p2-fin-live][rollback] missing payment_batch_v1");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (flag.overrideMode === "force_off" && flag.effectiveEnabled === false) {
|
||||
console.log("[p2-fin-live][rollback] payment_batch_v1 already force_off");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const updated = await fetchJson(`${baseUrl}/api/system/feature-flags/payment_batch_v1`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
"content-type": "application/json",
|
||||
accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
overrideMode: "force_off",
|
||||
rolloutType: "global",
|
||||
departmentIds: [],
|
||||
note: `${issueId} runner final rollback`,
|
||||
expectedVersion: flag.version,
|
||||
}),
|
||||
});
|
||||
if (!updated.response.ok) {
|
||||
console.error(`[p2-fin-live][rollback] update failed: ${errorSummary(updated.json, updated.response.status)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const item = updated.json?.data?.item;
|
||||
console.log(`[p2-fin-live][rollback] payment_batch_v1 override=${item?.overrideMode ?? "unknown"} effective=${item?.effectiveEnabled ?? "unknown"}`);
|
||||
NODE
|
||||
restore_payment_batch_flag_snapshot() {
|
||||
P2_FIN_LIVE_APPROVED=1 LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" \
|
||||
node "$ROOT_DIR/scripts/qa/payment-batch-flag-snapshot.mjs" restore \
|
||||
--snapshot-file "$FLAG_SNAPSHOT_FILE"
|
||||
}
|
||||
|
||||
write_summary_json() {
|
||||
@@ -146,27 +65,18 @@ write_summary_json() {
|
||||
QA_BASE_URL="$PUBLIC_BASE_URL" \
|
||||
QA_MODE="p2-fin-live-window" \
|
||||
QA_STATUS="$qa_status" \
|
||||
QA_SUMMARY_NOTES="${QA_ISSUE_ID:-P2FLIVE} 公网短窗口:临时启用 payment_batch_v1,完成测试付款闭环后强制回滚到 flag_force_off;不部署、不接真实支付、不开放图片 OCR。" \
|
||||
QA_SUMMARY_NOTES="${QA_ISSUE_ID:-P2FLIVE} 公网短窗口:临时启用 payment_batch_v1,结束后恢复调用前完整快照;不部署、不接真实支付、不开放图片 OCR。" \
|
||||
node "$ROOT_DIR/scripts/qa/write-qa-summary.mjs" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
finish() {
|
||||
if [[ "${P2_FIN_LIVE_APPROVED:-0}" == "1" ]]; then
|
||||
echo ">>> [p2-fin-live] final-force-flag-off" | tee -a "$SUMMARY_TXT"
|
||||
if force_payment_batch_flag_off > >(tee "$REPORT_DIR/final-force-flag-off.log") 2>&1; then
|
||||
echo "[p2-fin-live][PASS] final-force-flag-off" | tee -a "$SUMMARY_TXT"
|
||||
if [[ "${P2_FIN_LIVE_APPROVED:-0}" == "1" && -f "$FLAG_SNAPSHOT_FILE" ]]; then
|
||||
echo ">>> [p2-fin-live] restore-caller-flag-snapshot" | tee -a "$SUMMARY_TXT"
|
||||
if restore_payment_batch_flag_snapshot > >(tee "$REPORT_DIR/restore-caller-flag-snapshot.log") 2>&1; then
|
||||
echo "[p2-fin-live][PASS] restore-caller-flag-snapshot" | tee -a "$SUMMARY_TXT"
|
||||
else
|
||||
local code="$?"
|
||||
echo "[p2-fin-live][FAIL] final-force-flag-off exit=$code" | tee -a "$SUMMARY_TXT"
|
||||
STATUS=1
|
||||
fi
|
||||
|
||||
echo ">>> [p2-fin-live] final-flag-off-check" | tee -a "$SUMMARY_TXT"
|
||||
if LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" node "$ROOT_DIR/scripts/qa/check-payment-batch-flag-off.mjs" --json > >(tee "$REPORT_DIR/final-flag-off-check.log") 2>&1; then
|
||||
echo "[p2-fin-live][PASS] final-flag-off-check" | tee -a "$SUMMARY_TXT"
|
||||
else
|
||||
local code="$?"
|
||||
echo "[p2-fin-live][FAIL] final-flag-off-check exit=$code" | tee -a "$SUMMARY_TXT"
|
||||
echo "[p2-fin-live][FAIL] restore-caller-flag-snapshot exit=$code" | tee -a "$SUMMARY_TXT"
|
||||
STATUS=1
|
||||
fi
|
||||
fi
|
||||
@@ -187,8 +97,13 @@ if [[ "${P2_FIN_LIVE_APPROVED:-0}" != "1" ]]; then
|
||||
fi
|
||||
|
||||
run_shell_step "live-env-strict" "LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' node '$ROOT_DIR/scripts/qa/check-live-smoke-env.mjs' --strict"
|
||||
run_shell_step "pre-flag-off-check" "LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' node '$ROOT_DIR/scripts/qa/check-payment-batch-flag-off.mjs' --json"
|
||||
run_shell_step "p2-fin-live-spec" "cd '$CLIENT_DIR' && PLAYWRIGHT_MANUAL=1 E2E_MODE=live P2_FIN_LIVE_APPROVED=1 QA_ISSUE_ID='${QA_ISSUE_ID:-P2FLIVE}' P2_FIN_LIVE_MARKER_PREFIX='${P2_FIN_LIVE_MARKER_PREFIX:-${QA_ISSUE_ID:-P2FLIVE}}' P2_FIN_LIVE_REPORT_DIR='$REPORT_DIR' LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' npx playwright test tests/e2e/manual/p2-fin-live-payment-batch.spec.ts --workers=1 --reporter=list"
|
||||
[[ "$STATUS" -eq 0 ]] || exit "$STATUS"
|
||||
|
||||
run_shell_step "capture-caller-flag-snapshot" "P2_FIN_LIVE_APPROVED=1 LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' node '$ROOT_DIR/scripts/qa/payment-batch-flag-snapshot.mjs' capture --snapshot-file '$FLAG_SNAPSHOT_FILE'"
|
||||
[[ "$STATUS" -eq 0 ]] || exit "$STATUS"
|
||||
|
||||
ALREADY_ON_MODE="$(node -e 'const snapshot = require(process.argv[1]); process.stdout.write(snapshot.effectiveEnabled === true ? "1" : "0")' "$FLAG_SNAPSHOT_FILE")"
|
||||
run_shell_step "p2-fin-live-spec" "cd '$CLIENT_DIR' && PLAYWRIGHT_MANUAL=1 E2E_MODE=live P2_FIN_LIVE_APPROVED=1 P2_FIN_LIVE_FINAL_STATE=restore P2_FIN_LIVE_ALREADY_ON='$ALREADY_ON_MODE' QA_ISSUE_ID='${QA_ISSUE_ID:-P2FLIVE}' P2_FIN_LIVE_MARKER_PREFIX='${P2_FIN_LIVE_MARKER_PREFIX:-${QA_ISSUE_ID:-P2FLIVE}}' P2_FIN_LIVE_REPORT_DIR='$REPORT_DIR' LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' npx playwright test tests/e2e/manual/p2-fin-live-payment-batch.spec.ts --workers=1 --reporter=list"
|
||||
|
||||
if [[ "${P2_FIN_LIVE_SKIP_MOBILE:-0}" == "1" ]]; then
|
||||
echo "[p2-fin-live][SKIP] mobile-wecom P2_FIN_LIVE_SKIP_MOBILE=1" | tee -a "$SUMMARY_TXT"
|
||||
|
||||
在新工单中引用
屏蔽一个用户