文件
hyb-oa/server/tests/unit/scripts/live-smoke-readiness-report.test.js
T

179 行
6.2 KiB
JavaScript

const { execFile } = require("node:child_process");
const { mkdtemp, readFile, rm } = require("node:fs/promises");
const http = require("node:http");
const os = require("node:os");
const path = require("node:path");
const { promisify } = require("node:util");
const { pathToFileURL } = require("node:url");
const execFileAsync = promisify(execFile);
const ROOT_DIR = path.resolve(__dirname, "../../../..");
let readinessReport;
function passSteps() {
return Object.fromEntries(
readinessReport.READINESS_STEP_NAMES.map((name) => [name, {
status: "PASS",
kind: "required",
findingCount: 0,
logRef: `artifacts/qa-flow/test/${name}.log`,
}]),
);
}
function listen(server) {
return new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", () => {
server.off("error", reject);
resolve();
});
});
}
function close(server) {
return new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
}
const EXPECTED_BUILD_SHA = "abcdef123456";
describe("live smoke readiness aggregation", () => {
beforeAll(async () => {
const scriptPath = path.join(ROOT_DIR, "scripts/qa/live-smoke-readiness-report.mjs");
readinessReport = await import(pathToFileURL(scriptPath).href);
});
it.each([
["missing database", "pendingOcr"],
["missing login credentials", "liveSmoke"],
["missing OCR sample", "liveOcr"],
])("propagates %s as PASS_WITH_FINDINGS", (_label, stepName) => {
const steps = passSteps();
steps[stepName] = {
...steps[stepName],
status: "SKIP",
findingCount: 1,
};
const result = readinessReport.evaluateReadinessSteps(steps);
expect(result.status).toBe("PASS_WITH_FINDINGS");
expect(result.findingCount).toBeGreaterThan(0);
expect(result.findings).toEqual(expect.arrayContaining([
expect.objectContaining({ source: stepName, severity: "manual_or_config" }),
]));
});
it("reports clean PASS only when every prerequisite and step is ready", () => {
expect(readinessReport.evaluateReadinessSteps(passSteps())).toMatchObject({
status: "PASS",
findingCount: 0,
findings: [],
});
});
it("treats a required command failure as a blocker", () => {
const steps = passSteps();
steps.publicReady = { ...steps.publicReady, status: "FAIL", findingCount: 1 };
expect(readinessReport.evaluateReadinessSteps(steps)).toMatchObject({
status: "FAIL",
findingCount: 1,
});
});
it("runs the shell parent against a local public stub and writes a redacted non-clean summary", async () => {
const reportDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-readiness-test-"));
const server = http.createServer((request, response) => {
response.setHeader("content-type", "application/json; charset=utf-8");
if (request.url === "/ready") {
response.end(JSON.stringify({ data: { ok: true, buildSha: EXPECTED_BUILD_SHA } }));
return;
}
if (request.url === "/api/auth/wecom/config") {
response.end(JSON.stringify({
data: {
status: "ready",
entryEnabled: true,
oauthEnabled: true,
defaultReturnTo: "/portal",
loginUrl: "/api/auth/wecom/login",
},
}));
return;
}
if (request.url === "/api/auth/capabilities") {
response.end(JSON.stringify({
data: {
emailDelivery: { ready: true, statusReasons: [] },
emailLoginCode: { enabled: true },
passwordReset: { enabled: true },
},
}));
return;
}
if (request.url === "/") {
response.setHeader("content-type", "text/html; charset=utf-8");
response.end('<!doctype html><html><body>OA<script src="/assets/index-test.js"></script></body></html>');
return;
}
if (request.url === "/assets/index-test.js") {
response.setHeader("content-type", "application/javascript; charset=utf-8");
response.end(`document.documentElement.dataset.buildSha="${EXPECTED_BUILD_SHA}";`);
return;
}
response.statusCode = 404;
response.end(JSON.stringify({ error: "not_found" }));
});
try {
await listen(server);
const address = server.address();
const env = { ...process.env };
for (const key of [
"DATABASE_URL",
"LIVE_SMOKE_ADMIN_IDENTIFIER",
"LIVE_SMOKE_ADMIN_PASSWORD",
"LIVE_SMOKE_EMPLOYEE_IDENTIFIER",
"LIVE_SMOKE_EMPLOYEE_PASSWORD",
"PLAYWRIGHT_OCR_LIVE_IDENTIFIER",
"PLAYWRIGHT_OCR_LIVE_PASSWORD",
"PLAYWRIGHT_OCR_LIVE_FILE",
"PLAYWRIGHT_OCR_LIVE_DIR",
"LIVE_OCR_INVOICE_FILE",
]) {
delete env[key];
}
env.LIVE_SMOKE_BASE_URL = `http://127.0.0.1:${address.port}`;
env.LIVE_SMOKE_READINESS_DIR = reportDir;
env.LIVE_SMOKE_EXPECTED_BUILD_SHA = EXPECTED_BUILD_SHA;
env.QA_ISSUE_ID = "READINESS-TEST";
const result = await execFileAsync("bash", [path.join(ROOT_DIR, "scripts/qa/run-live-smoke-readiness.sh")], {
cwd: ROOT_DIR,
env,
maxBuffer: 2 * 1024 * 1024,
});
const summaryText = await readFile(path.join(reportDir, "summary.json"), "utf8");
const summary = JSON.parse(summaryText);
expect(result.stdout).toContain("[live-readiness][PASS_WITH_FINDINGS]");
expect(summary.status).toBe("PASS_WITH_FINDINGS");
expect(summary.findingCount).toBeGreaterThan(0);
expect(summary.steps.pendingOcr.status).toBe("SKIP");
expect(summary.steps.liveSmoke.status).toBe("SKIP");
expect(summary.steps.liveOcr.status).toBe("SKIP");
expect(summary.steps.privacy.status).toBe("PASS");
expect(summary.expectedBuildSha).toBe(EXPECTED_BUILD_SHA);
expect(summary.observedBuildSha).toBe(EXPECTED_BUILD_SHA);
expect(summary.buildShaMatches).toBe(true);
expect(summary.childReports.pendingOcr.findingCount).toBeGreaterThan(0);
expect(summaryText).not.toContain(ROOT_DIR);
expect(summaryText).not.toMatch(/DATABASE_URL|LIVE_SMOKE_ADMIN_PASSWORD|PLAYWRIGHT_OCR_LIVE_FILE/);
} finally {
await close(server);
await rm(reportDir, { recursive: true, force: true });
}
}, 30_000);
});