文件
hyb-oa/scripts/qa/tests/check-flow-coverage.test.mjs
T

311 行
10 KiB
JavaScript

import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import test from "node:test";
import { buildReport, hasBlockingGaps } from "../check-flow-coverage.mjs";
const WITNESS_SPEC = `
import { test } from "@playwright/test";
export const LOCAL_CANDIDATE_MANIFEST_DRIVEN_WITNESSES = true;
for (const flow of localCandidateFlows) {
test(flow.witnessTag, async ({ page }) => {
installBrowserErrorMonitor(page);
installSyntheticApis(page, flow.roleFixture);
await loginToWitness(page, flow.witnessRoute);
await expectUniqueH1(page, flow.expectedH1);
await expectPrimaryActionContract(page, flow);
});
}
`;
function candidateFlow(bundle = "core-flow", witnessRoute = "/core", overrides = {}) {
return {
bundle,
witnessRoute,
witnessTag: `@local-candidate:${bundle}`,
expectedH1: "核心页面",
roleFixture: "employee",
primaryAction: { allowed: "开始", forbidden: "越权操作" },
witnessSpec: "client/tests/e2e/local-candidate-witnesses.spec.ts",
...overrides,
};
}
function writeFixture({
routes = ["/core"],
coreBundles = ["core-flow"],
manifestRoutes = [
{
path: "/core",
classification: "core",
bundle: "core-flow",
behaviorSpecs: ["client/tests/e2e/core.spec.ts"],
},
],
localCandidateFlows = [candidateFlow()],
acceptanceSpecs = ["client/tests/e2e/acceptance.spec.ts"],
specs = {},
} = {}) {
const root = mkdtempSync(path.join(tmpdir(), "hyb-flow-coverage-"));
const routerPath = path.join(root, "client/src/router/index.tsx");
const manifestPath = path.join(root, "scripts/qa/route-manifest.json");
mkdirSync(path.dirname(routerPath), { recursive: true });
mkdirSync(path.dirname(manifestPath), { recursive: true });
writeFileSync(
routerPath,
`export function RouterFixture() { return <Routes>${routes.map((route) => `<Route path="${route}" element={<div />} />`).join("")}</Routes>; }\n`,
"utf8",
);
writeFileSync(
manifestPath,
`${JSON.stringify({
schemaVersion: 1,
coreBundles,
acceptanceSpecs,
localCandidateFlows,
routes: manifestRoutes,
}, null, 2)}\n`,
"utf8",
);
const defaultSpecs = {
"client/tests/e2e/core.spec.ts": 'import { test } from "@playwright/test";\ntest("core", async ({ page }) => { await page.goto("/core"); });\n',
"client/tests/e2e/acceptance.spec.ts": 'import { test } from "@playwright/test";\ntest("acceptance", async ({ page }) => { await page.goto("/core"); });\n',
"client/tests/e2e/local-candidate-witnesses.spec.ts": WITNESS_SPEC,
...specs,
};
for (const [relativePath, source] of Object.entries(defaultSpecs)) {
if (source == null) continue;
const file = path.join(root, relativePath);
mkdirSync(path.dirname(file), { recursive: true });
writeFileSync(file, source, "utf8");
}
return root;
}
function runFixture(root) {
const report = buildReport(root);
return { status: hasBlockingGaps(report) ? 1 : 0, report };
}
function withFixture(options, callback) {
const root = writeFixture(options);
try {
callback(root);
} finally {
rmSync(root, { recursive: true, force: true });
}
}
test("完整 manifest、acceptance spec 与可信 witness 通过", () => {
withFixture({}, (root) => {
const result = runFixture(root);
assert.equal(result.status, 0);
assert.equal(result.report.summary.blockingGapCount, 0);
assert.equal(result.report.summary.coveredCoreBundleCount, 1);
assert.equal(result.report.summary.coveredWitnessCount, 1);
assert.equal(result.report.summary.validAcceptanceSpecCount, 1);
});
});
test("未分类注册路由必须阻断", () => {
withFixture({ routes: ["/core", "/unmapped"] }, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.unclassified, 1);
});
});
test("403 与 wildcard 注册路由不能从分类扫描中豁免", () => {
withFixture({ routes: ["/core", "/403", "*"] }, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.routeCount, 3);
assert.equal(result.report.summary.gapCounts.unclassified, 2);
});
});
test("manual 或 live spec 不能充当核心行为证据", () => {
withFixture({
manifestRoutes: [
{
path: "/core",
classification: "core",
bundle: "core-flow",
behaviorSpecs: ["client/tests/e2e/manual/core.spec.ts"],
},
],
specs: {
"client/tests/e2e/manual/core.spec.ts": 'import { test } from "@playwright/test";\ntest("manual", async ({ page }) => { await page.goto("/core"); });\n',
},
}, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.manual_only, 1);
});
});
test("核心路由缺失行为测试必须阻断", () => {
withFixture({
manifestRoutes: [{ path: "/core", classification: "core", bundle: "core-flow" }],
}, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.missing_behavior, 1);
});
});
test("visualRequired 缺失可执行视觉 spec 必须阻断", () => {
withFixture({
manifestRoutes: [
{
path: "/core",
classification: "core",
bundle: "core-flow",
behaviorSpecs: ["client/tests/e2e/core.spec.ts"],
visualRequired: true,
},
],
}, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.missing_visual, 1);
});
});
test("核心 bundle 缺失 witness 必须阻断", () => {
withFixture({ localCandidateFlows: [] }, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.missing_local_candidate_witness, 1);
});
});
test("重复 bundle 与重复 witness tag 必须阻断", () => {
withFixture({ localCandidateFlows: [candidateFlow(), candidateFlow()] }, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.duplicate_local_candidate_bundle, 1);
assert.equal(result.report.summary.gapCounts.duplicate_witness_tag, 1);
});
});
test("未知 local-candidate bundle 必须阻断", () => {
withFixture({
localCandidateFlows: [candidateFlow(), candidateFlow("unknown-flow", "/core")],
}, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.unknown_local_candidate_bundle, 1);
});
});
test("重复 core bundle 必须阻断", () => {
withFixture({ coreBundles: ["core-flow", "core-flow"] }, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.duplicate_core_bundle, 1);
});
});
test("orphan manifest 路由必须阻断", () => {
withFixture({
manifestRoutes: [
{
path: "/core",
classification: "core",
bundle: "core-flow",
behaviorSpecs: ["client/tests/e2e/core.spec.ts"],
},
{ path: "/orphan", classification: "supported-noncore" },
],
}, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.orphan_manifest_route, 1);
});
});
test("witnessRoute 指向未注册路由必须阻断", () => {
withFixture({ localCandidateFlows: [candidateFlow("core-flow", "/missing")] }, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.orphan_witness_route, 1);
});
});
test("witnessRoute 绑定到其他 bundle 必须阻断", () => {
const otherSpec = 'import { test } from "@playwright/test";\ntest("other", async ({ page }) => { await page.goto("/other"); });\n';
withFixture({
routes: ["/core", "/other"],
coreBundles: ["core-flow", "other-flow"],
manifestRoutes: [
{
path: "/core",
classification: "core",
bundle: "core-flow",
behaviorSpecs: ["client/tests/e2e/core.spec.ts"],
},
{
path: "/other",
classification: "core",
bundle: "other-flow",
behaviorSpecs: ["client/tests/e2e/other.spec.ts"],
},
],
localCandidateFlows: [candidateFlow("core-flow", "/other"), candidateFlow("other-flow", "/other")],
specs: { "client/tests/e2e/other.spec.ts": otherSpec },
}, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.wrong_witness_association, 1);
});
});
test("witness 字段不完整或 tag 不稳定必须阻断", () => {
withFixture({
localCandidateFlows: [candidateFlow("core-flow", "/core", {
witnessTag: "@local-candidate:wrong",
expectedH1: "",
roleFixture: "unknown",
primaryAction: { allowed: "相同", forbidden: "相同" },
})],
}, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.invalid_witness_tag, 1);
assert.equal(result.report.summary.gapCounts.missing_expected_h1, 1);
assert.equal(result.report.summary.gapCounts.invalid_role_fixture, 1);
assert.equal(result.report.summary.gapCounts.invalid_primary_action, 1);
});
});
test("manual/live acceptance spec 必须阻断", () => {
withFixture({
acceptanceSpecs: ["client/tests/e2e/manual/acceptance.spec.ts"],
specs: {
"client/tests/e2e/manual/acceptance.spec.ts": 'import { test } from "@playwright/test";\ntest("manual", async () => {});\n',
},
}, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.equal(result.report.summary.gapCounts.manual_or_live_acceptance_spec, 1);
});
});
test("候选 spec 不同路径使用同一 basename 必须阻断", () => {
withFixture({
acceptanceSpecs: ["client/tests/e2e/nested/local-candidate-witnesses.spec.ts"],
specs: {
"client/tests/e2e/nested/local-candidate-witnesses.spec.ts":
'import { test } from "@playwright/test";\ntest("acceptance", async ({ page }) => { await page.goto("/core"); });\n',
},
}, (root) => {
const result = runFixture(root);
assert.equal(result.status, 1);
assert.deepEqual(result.report.duplicateCandidateSpecBasenames, ["local-candidate-witnesses.spec.ts"]);
assert.equal(result.report.summary.gapCounts.duplicate_candidate_spec_basename, 1);
});
});