151 lines
7.5 KiB
JavaScript
151 lines
7.5 KiB
JavaScript
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { failureCount, gates, featureManifestInventoryScanRoots, renderReport, reportPath, runFeatureManifestInventory } from "./verify-feature-manifest-baseline.mjs";
|
|
|
|
const focusedSmokeTargets = ["scripts/check-api-inventory.test.ts", "apps/web/app/application/featureManifests.test.ts"];
|
|
|
|
describe("Feature Manifest baseline runner configuration", () => {
|
|
it("defines the required non-E2E gates and report path", () => {
|
|
expect(reportPath).toMatch(/\.tc\/baselines\/feature-manifest\/baseline-health\.md$/);
|
|
expect(gates.map((gate) => gate.id)).toEqual([
|
|
"typecheck",
|
|
"lint",
|
|
"vitest-featureManifest-smoke",
|
|
"featureManifest-core-build-without-plugins-smoke",
|
|
"featureManifest-removed-modules",
|
|
"featureManifest-feature-manifest-inventory",
|
|
]);
|
|
expect(gates.some((gate) => gate.args?.some((arg) => /playwright|cypress|browser|e2e/i.test(arg)))).toBe(false);
|
|
expect(featureManifestInventoryScanRoots).toEqual(["docs/feature-inventory.md", "docs/feature-manifests"]);
|
|
});
|
|
|
|
it("keeps focused smoke tests in the Vitest gate", () => {
|
|
const vitestGate = gates.find((gate) => gate.id === "vitest-featureManifest-smoke");
|
|
|
|
expect(vitestGate?.args).toEqual(["exec", "vitest", "run", ...focusedSmokeTargets]);
|
|
});
|
|
});
|
|
|
|
describe("Feature Manifest baseline runner failure counting", () => {
|
|
it("counts failed typecheck diagnostics conservatively", () => {
|
|
expect(failureCount("typecheck", "src/a.ts(1,1): error TS2307: Cannot find module\nsrc/b.ts(2,2): error TS2322: Type mismatch", "", 1)).toBe(2);
|
|
expect(failureCount("typecheck", "Build failed", "", 1)).toBe(1);
|
|
});
|
|
|
|
it("counts failed ESLint diagnostics conservatively", () => {
|
|
expect(failureCount("lint", "✖ 4 problems (3 errors, 1 warning)", "", 1)).toBe(3);
|
|
expect(failureCount("lint", "2 errors and 0 warnings", "", 1)).toBe(2);
|
|
expect(failureCount("lint", "Lint crashed", "", 1)).toBe(1);
|
|
});
|
|
|
|
it("counts failed Vitest diagnostics conservatively", () => {
|
|
expect(failureCount("vitest-featureManifest-smoke", "Tests 2 failed | 8 passed", "", 1)).toBe(2);
|
|
expect(failureCount("vitest-featureManifest-smoke", "Test Files 1 failed | 2 passed", "", 1)).toBe(1);
|
|
expect(failureCount("vitest-featureManifest-smoke", "Vitest crashed", "", 1)).toBe(1);
|
|
});
|
|
|
|
it("returns zero for clean exits", () => {
|
|
expect(failureCount("lint", "warning only", "", 0)).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("Feature Manifest inventory scan", () => {
|
|
it("scans existing roots, records missing roots, counts manifest signals, and ignores dependency and TC paths", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "featureManifest-baseline-inventory-"));
|
|
await mkdir(join(root, "docs", "feature-manifests"), { recursive: true });
|
|
await mkdir(join(root, "docs", "feature-manifests", "node_modules", "ignored"), { recursive: true });
|
|
await mkdir(join(root, "docs", "feature-manifests", ".tc"), { recursive: true });
|
|
await mkdir(join(root, "docs", "feature-manifests", "generated"), { recursive: true });
|
|
|
|
await writeFile(
|
|
join(root, "docs", "feature-inventory.md"),
|
|
[
|
|
"# Feature inventory",
|
|
"## Core behalten",
|
|
"## Optional/Plugin",
|
|
"Removal/Review mentions remove-from-core.",
|
|
"Postmark, Rewardful, PageBlocks and Site-Demo are provider/demo references.",
|
|
].join("\n"),
|
|
"utf8",
|
|
);
|
|
await writeFile(join(root, "docs", "feature-manifests", "core.json"), JSON.stringify({ classification: "core", title: "Minimal Boilerplate Core" }), "utf8");
|
|
await writeFile(join(root, "docs", "feature-manifests", "plugin.json"), JSON.stringify({ classification: "plugin-candidate", rationale: "extract-plugin" }), "utf8");
|
|
await writeFile(join(root, "docs", "feature-manifests", "remove.json"), JSON.stringify({ classification: "remove-candidate", rationale: "remove-from-core" }), "utf8");
|
|
await writeFile(join(root, "docs", "feature-manifests", "node_modules", "ignored", "ignored.json"), "Postmark core plugin remove", "utf8");
|
|
await writeFile(join(root, "docs", "feature-manifests", ".tc", "ignored.json"), "Postmark core plugin remove", "utf8");
|
|
await writeFile(join(root, "docs", "feature-manifests", "generated", "ignored.json"), "Postmark core plugin remove", "utf8");
|
|
|
|
const inventory = await runFeatureManifestInventory(root);
|
|
const allMatches = inventory.matches.join("\n");
|
|
|
|
expect(inventory.roots).toEqual(["docs/feature-inventory.md", "docs/feature-manifests"]);
|
|
expect(inventory.missingRoots).toEqual([]);
|
|
expect(inventory.filesScanned).toBe(4);
|
|
expect(inventory.counts).toMatchObject({
|
|
"core-manifest-signal": 5,
|
|
"plugin-manifest-signal": 3,
|
|
"remove-manifest-signal": 4,
|
|
"json-files": 3,
|
|
"postmark-reference": 1,
|
|
"rewardful-reference": 1,
|
|
"pageblocks-reference": 1,
|
|
"site-demo-reference": 1,
|
|
});
|
|
expect(allMatches).toContain("docs/feature-inventory.md");
|
|
expect(allMatches).toContain("docs/feature-manifests/core.json");
|
|
expect(allMatches).not.toContain("node_modules/ignored/ignored.json");
|
|
expect(allMatches).not.toContain(".tc/ignored.json");
|
|
expect(allMatches).not.toContain("generated/ignored.json");
|
|
});
|
|
|
|
it("records absent manifest roots as inventory integrity failures", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "featureManifest-baseline-missing-"));
|
|
const inventory = await runFeatureManifestInventory(root);
|
|
|
|
expect(inventory.roots).toEqual([]);
|
|
expect(inventory.missingRoots).toEqual(["docs/feature-inventory.md", "docs/feature-manifests"]);
|
|
expect(inventory.filesScanned).toBe(0);
|
|
expect(inventory.totalMatches).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("Feature Manifest report rendering", () => {
|
|
it("renders gate metadata, fingerprints, and inventory counts", () => {
|
|
const report = renderReport([
|
|
{ id: "typecheck", command: "pnpm run typecheck", exitCode: 2, durationMs: 1500, failureCount: 7, fingerprint: { hash: "abc123def456", excerpt: "error TS2322" } },
|
|
{
|
|
id: "featureManifest-feature-manifest-inventory",
|
|
command: "read-only Feature Manifest feature-manifest/plugin-baseline inventory",
|
|
exitCode: 1,
|
|
durationMs: 42,
|
|
failureCount: 1,
|
|
inventory: {
|
|
roots: ["docs/feature-inventory.md"],
|
|
missingRoots: ["docs/feature-manifests"],
|
|
filesScanned: 1,
|
|
counts: { "core-manifest-signal": 1, "plugin-manifest-signal": 2, "json-files": 0 },
|
|
totalMatches: 3,
|
|
matches: [],
|
|
},
|
|
fingerprint: { hash: "fed456abc123", excerpt: "core-manifest-signal: 1" },
|
|
},
|
|
]);
|
|
|
|
expect(report).toContain("# Feature Manifest Baseline Health");
|
|
expect(report).toContain("non-E2E baseline gates for Feature Manifest baseline");
|
|
expect(report).toContain("feature-manifest/plugin-baseline signals");
|
|
expect(report).toContain("## typecheck");
|
|
expect(report).toContain("## featureManifest-feature-manifest-inventory");
|
|
expect(report).toContain("- Exitcode: 2");
|
|
expect(report).toContain("- Failure Count: 7");
|
|
expect(report).toContain("- Output Fingerprint: abc123def456");
|
|
expect(report).toContain("- Inventory Roots: docs/feature-inventory.md");
|
|
expect(report).toContain("- Missing Roots: docs/feature-manifests");
|
|
expect(report).toContain(" - core-manifest-signal: 1");
|
|
expect(report).toContain("- Total Inventory Matches: 3");
|
|
});
|
|
});
|