import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, writeFileSync, rmSync, symlinkSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { fullLocalSpecFiles, verifyFullLocalDiscovery } from "../verify-full-local-discovery.mjs"; function fixture(context) { const root = mkdtempSync(path.join(os.tmpdir(), "hyb-discovery-test-")); context.after(() => rmSync(root, { recursive: true, force: true })); const files = ["pwa-cross-version-upgrade.spec.ts", "nested/ordinary.test.ts", "live-smoke.spec.ts", "manual/private.spec.ts"]; for (const file of files) { const target = path.join(root, "client/tests/e2e", file); mkdirSync(path.dirname(target), { recursive: true }); writeFileSync(target, ""); } return root; } function report(files) { return { errors: [], suites: files.map((file, index) => ({ file, specs: [{ file, id: `case-${index}`, title: `case ${index}`, line: 1, tests: [{ projectName: "" }] }] })), }; } test("归属来自源码:包含专用输入和 live 名称,不包含 manual", (context) => { const root = fixture(context); const files = fullLocalSpecFiles(root); assert.equal(files.length, 3); assert.ok(files.some((file) => file.endsWith("pwa-cross-version-upgrade.spec.ts"))); assert.ok(files.some((file) => file.endsWith("live-smoke.spec.ts"))); const summary = verifyFullLocalDiscovery(root, report(files)); assert.equal(summary.status, "PASS"); assert.equal(summary.kind, "FULL_LOCAL_DISCOVERY_ONLY"); assert.equal(summary.cases.length, 3); }); test("只报 manifest 子集或排除 PWA 必须失败", (context) => { const root = fixture(context); const files = fullLocalSpecFiles(root).filter((file) => !file.includes("pwa-cross-version")); const summary = verifyFullLocalDiscovery(root, report(files)); assert.equal(summary.status, "FAIL"); assert.deepEqual(summary.missingSpecs, ["client/tests/e2e/pwa-cross-version-upgrade.spec.ts"]); }); test("新增非 manual 文件不能被旧清单遗漏", (context) => { const root = fixture(context); const original = report(fullLocalSpecFiles(root)); writeFileSync(path.join(root, "client/tests/e2e/newly-added.spec.ts"), ""); assert.deepEqual(verifyFullLocalDiscovery(root, original).missingSpecs, ["client/tests/e2e/newly-added.spec.ts"]); }); test("完整文件集合也不能掩盖收集错误或零用例", (context) => { const root = fixture(context); const failed = report(fullLocalSpecFiles(root)); failed.errors.push({ message: "PWA_BUILD_A_DIR_missing" }); assert.ok(verifyFullLocalDiscovery(root, failed).issues.includes("discovery_errors")); failed.errors = []; failed.suites[0].specs[0].tests = []; assert.ok(verifyFullLocalDiscovery(root, failed).issues.includes("spec_without_cases")); assert.equal(verifyFullLocalDiscovery(root, {}).status, "FAIL"); }); test("manual、未知或越界来源不能冒充完整集合", (context) => { const root = fixture(context); for (const extra of ["manual/private.spec.ts", "unknown.spec.ts", "../../../outside.spec.ts"]) { const summary = verifyFullLocalDiscovery(root, report([...fullLocalSpecFiles(root), extra])); assert.equal(summary.status, "FAIL"); assert.ok(summary.issues.includes("unexpected_specs")); } }); test("支持 Playwright 的根相对、测试目录相对与绝对文件来源", (context) => { const root = fixture(context); const files = fullLocalSpecFiles(root); const sources = [files[0].replace(/^client\//u, ""), files[1].replace(/^client\/tests\/e2e\//u, ""), path.join(root, files[2])]; assert.equal(verifyFullLocalDiscovery(root, report(sources)).status, "PASS"); }); test("符号链接必须有明确归属,不能静默跳过", (context) => { const root = fixture(context); symlinkSync("live-smoke.spec.ts", path.join(root, "client/tests/e2e/linked.spec.ts")); assert.throws(() => fullLocalSpecFiles(root), /explicit_ownership/u); });