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