文件
hyb-oa/scripts/qa/verify-full-local-discovery.mjs

85 行
3.6 KiB
JavaScript

#!/usr/bin/env node
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
const TEST_FILE_PATTERN = /\.(?:spec|test)\.[cm]?[jt]sx?$/u;
export function fullLocalSpecFiles(root) {
const files = [];
function visit(directory) {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (entry.name !== "manual") visit(path.join(directory, entry.name));
} else if (entry.isFile() && TEST_FILE_PATTERN.test(entry.name)) {
files.push(path.relative(root, path.join(directory, entry.name)).split(path.sep).join("/"));
} else if (entry.isSymbolicLink()) {
throw new Error("full_local_symlink_requires_explicit_ownership");
}
}
}
visit(path.join(root, "client/tests/e2e"));
return files.sort();
}
function sourceFile(root, file) {
if (typeof file !== "string" || !file) return "invalid_source";
let relative = path.isAbsolute(file) ? path.relative(root, file) : file;
relative = relative.replaceAll("\\", "/");
if (relative.startsWith("tests/e2e/")) relative = `client/${relative}`;
if (!relative.startsWith("client/tests/e2e/")) relative = `client/tests/e2e/${relative}`;
relative = path.posix.normalize(relative);
return relative.startsWith("client/tests/e2e/") ? relative : "invalid_source";
}
export function verifyFullLocalDiscovery(root, report) {
const expectedSpecs = fullLocalSpecFiles(root);
const cases = [];
const observed = new Set();
const issues = [];
function visit(suites, inheritedFile = "") {
for (const suite of Array.isArray(suites) ? suites : []) {
const suiteFile = suite.file ?? inheritedFile;
for (const spec of Array.isArray(suite.specs) ? suite.specs : []) {
const file = sourceFile(root, spec.file ?? suiteFile);
observed.add(file);
if (!Array.isArray(spec.tests) || !spec.tests.length) issues.push("spec_without_cases");
for (const test of Array.isArray(spec.tests) ? spec.tests : []) {
cases.push({ file, id: spec.id, title: spec.title, line: spec.line, projectName: test.projectName ?? "", repeatEachIndex: test.repeatEachIndex ?? 0 });
}
}
visit(suite.suites, suiteFile);
}
}
visit(report?.suites);
if (!Array.isArray(report?.suites) || !Array.isArray(report?.errors)) issues.push("invalid_discovery_report");
if (report?.errors?.length) issues.push("discovery_errors");
if (!expectedSpecs.length || !cases.length) issues.push("empty_discovery");
const missingSpecs = expectedSpecs.filter((file) => !observed.has(file));
const unexpectedSpecs = [...observed].filter((file) => !expectedSpecs.includes(file)).sort();
if (missingSpecs.length) issues.push("missing_specs");
if (unexpectedSpecs.length) issues.push("unexpected_specs");
return {
status: issues.length ? "FAIL" : "PASS",
kind: "FULL_LOCAL_DISCOVERY_ONLY",
expectedSpecs,
observedSpecs: [...observed].sort(),
missingSpecs,
unexpectedSpecs,
cases,
issues: [...new Set(issues)],
};
}
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
try {
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const summary = verifyFullLocalDiscovery(root, JSON.parse(readFileSync(process.argv[2], "utf8")));
console.log(JSON.stringify(summary, null, 2));
process.exitCode = summary.status === "PASS" ? 0 : 1;
} catch {
console.error("[full-local-discovery] report_or_source_invalid");
process.exitCode = 1;
}
}