179 行
6.2 KiB
JavaScript
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);
|
|
});
|