文件
hyb-oa/scripts/qa/check-payment-batch-flag-off.mjs
T

213 行
5.8 KiB
JavaScript
可执行文件

#!/usr/bin/env node
const DEFAULT_BASE_URL = "https://oa.hyinda.com";
function parseArgs(argv) {
const options = {
baseUrl: process.env.LIVE_SMOKE_BASE_URL || DEFAULT_BASE_URL,
json: false,
strict: false,
};
for (const arg of argv) {
if (arg === "--json") {
options.json = true;
} else if (arg === "--strict") {
options.strict = true;
} else if (arg.startsWith("--base-url=")) {
options.baseUrl = arg.slice("--base-url=".length);
}
}
options.baseUrl = options.baseUrl.trim().replace(/\/$/, "");
return options;
}
function getCredential(role) {
const prefixByRole = {
finance: "LIVE_SMOKE_FINANCE",
employee: "LIVE_SMOKE_EMPLOYEE",
admin: "LIVE_SMOKE_ADMIN",
};
const prefix = prefixByRole[role];
const identifier = process.env[`${prefix}_IDENTIFIER`]?.trim();
const password = process.env[`${prefix}_PASSWORD`]?.trim();
if (!identifier || !password) {
return null;
}
return { identifier, password };
}
async function fetchJson(url, init = {}) {
const response = await fetch(url, {
...init,
signal: AbortSignal.timeout(15_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 summarizeError(json) {
const error = json?.error ?? {};
const details = error.details ?? {};
return {
code: typeof error.code === "string" ? error.code : null,
featureFlag: typeof details.featureFlag === "string" ? details.featureFlag : null,
reason: typeof details.reason === "string" ? details.reason : null,
};
}
async function login(baseUrl, role, credential) {
const response = await fetchJson(`${baseUrl}/api/auth/login`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-device-fingerprint": `p2af-flag-off-${role}`,
},
body: JSON.stringify({
identifier: credential.identifier,
password: credential.password,
}),
});
const accessToken = response.json?.data?.accessToken;
if (!response.ok || typeof accessToken !== "string" || !accessToken) {
return {
ok: false,
role,
status: response.status,
error: summarizeError(response.json),
};
}
return {
ok: true,
role,
accessToken,
};
}
async function probeEndpoint(baseUrl, role, accessToken, endpoint) {
const response = await fetchJson(`${baseUrl}${endpoint}`, {
method: "GET",
headers: {
authorization: `Bearer ${accessToken}`,
accept: "application/json",
},
});
const error = summarizeError(response.json);
const featureFlagDisabled = response.status === 409 && error.featureFlag === "payment_batch_v1_disabled";
const permissionDenied = response.status === 403 && error.code === "PERM_001";
const unexpectedOpen = response.status === 200;
const acceptable = featureFlagDisabled || (role !== "finance" && permissionDenied);
return {
role,
endpoint,
httpStatus: response.status,
status: acceptable ? "PASS" : "FAIL",
featureFlagDisabled,
permissionDenied,
unexpectedOpen,
error,
};
}
function buildRolePlan() {
return [
{ role: "finance", endpoints: ["/api/payment-batches", "/api/ap-operations"] },
{ role: "employee", endpoints: ["/api/payment-batches"] },
{ role: "admin", endpoints: ["/api/payment-batches"] },
];
}
function printHuman(summary) {
console.log(`[payment-batch-flag-off] status=${summary.status} base=${summary.baseUrl}`);
for (const missing of summary.missingCredentials) {
console.log(`[payment-batch-flag-off] ${missing.role} status=SKIP reason=missing_credentials`);
}
for (const loginFailure of summary.loginFailures) {
console.log(`[payment-batch-flag-off] ${loginFailure.role} status=FAIL loginStatus=${loginFailure.status}`);
}
for (const result of summary.results) {
const detail = result.featureFlagDisabled
? "featureFlag=payment_batch_v1_disabled"
: result.permissionDenied
? "permission=denied"
: `error=${result.error.code || "-"}`;
console.log(
`[payment-batch-flag-off] ${result.role} ${result.endpoint} status=${result.status} http=${result.httpStatus} ${detail}`,
);
}
}
async function main() {
const options = parseArgs(process.argv.slice(2));
const missingCredentials = [];
const loginFailures = [];
const results = [];
for (const item of buildRolePlan()) {
const credential = getCredential(item.role);
if (!credential) {
missingCredentials.push({ role: item.role });
continue;
}
const session = await login(options.baseUrl, item.role, credential);
if (!session.ok) {
loginFailures.push(session);
continue;
}
for (const endpoint of item.endpoints) {
results.push(await probeEndpoint(options.baseUrl, item.role, session.accessToken, endpoint));
}
}
const hasFailures = loginFailures.length > 0 || results.some((result) => result.status !== "PASS");
const status = hasFailures ? "FAIL" : missingCredentials.length ? "SKIP" : results.length ? "PASS" : "SKIP";
const summary = {
checkedAt: new Date().toISOString(),
baseUrl: options.baseUrl,
status,
expectedFeatureFlag: "payment_batch_v1",
expectedState: "off",
readOnly: true,
missingCredentials,
loginFailures: loginFailures.map((item) => ({
role: item.role,
status: item.status,
error: item.error,
})),
results,
};
if (options.json) {
console.log(JSON.stringify(summary, null, 2));
} else {
printHuman(summary);
}
if (status === "FAIL" || (options.strict && status !== "PASS")) {
process.exit(1);
}
}
main().catch((error) => {
console.error(`[payment-batch-flag-off][ERROR] ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
});