#!/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); });