fix(conformance): fail the CI gate on unparseable manifests
parseManifestUseCases returning null for a manifest that exists made the cross-feature gate silently skip that feature. findUnparseableManifests now runs before the empty-graph early exit (an unparseable manifest contributes zero events and would otherwise pass as nothing-to-check) and any hit fails the run. Reader-closure from the downstream fork is not ported: this tree has no reads: manifests or ./reader exports yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,12 @@ import { describe, it, expect } from "vitest";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
import fs from "node:fs";
|
||||
import { findAllManifests, buildEventGraph, findOrphanConsumers } from "./conformance.mjs";
|
||||
import {
|
||||
findAllManifests,
|
||||
findUnparseableManifests,
|
||||
buildEventGraph,
|
||||
findOrphanConsumers,
|
||||
} from "./conformance.mjs";
|
||||
|
||||
function makeRepo(features) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "conformance-"));
|
||||
@@ -10,8 +15,9 @@ function makeRepo(features) {
|
||||
const dir = path.join(root, "packages", name, "src");
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
const useCasesStr = Object.entries(useCases)
|
||||
.map(([ucName, uc]) =>
|
||||
` ${ucName}: { mutates: ${uc.mutates ?? false}, audits: [], publishes: [${(uc.publishes ?? []).map((p) => `"${p}"`).join(", ")}], consumes: [${(uc.consumes ?? []).map((c) => `"${c}"`).join(", ")}] },`,
|
||||
.map(
|
||||
([ucName, uc]) =>
|
||||
` ${ucName}: { mutates: ${uc.mutates ?? false}, audits: [], publishes: [${(uc.publishes ?? []).map((p) => `"${p}"`).join(", ")}], consumes: [${(uc.consumes ?? []).map((c) => `"${c}"`).join(", ")}] },`,
|
||||
)
|
||||
.join("\n");
|
||||
fs.writeFileSync(
|
||||
@@ -43,11 +49,34 @@ describe("conformance script", () => {
|
||||
|
||||
it("skips packages without a manifest", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "conformance-empty-"));
|
||||
fs.mkdirSync(path.join(root, "packages", "no-manifest", "src"), { recursive: true });
|
||||
fs.mkdirSync(path.join(root, "packages", "no-manifest", "src"), {
|
||||
recursive: true,
|
||||
});
|
||||
expect(findAllManifests(root)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findUnparseableManifests", () => {
|
||||
it("flags a manifest the AST parser cannot read", () => {
|
||||
const root = makeRepo({ auth: { signIn: {} } });
|
||||
const brokenDir = path.join(root, "packages", "broken", "src");
|
||||
fs.mkdirSync(brokenDir, { recursive: true });
|
||||
// No defineFeature call — parseManifestUseCases returns null.
|
||||
fs.writeFileSync(
|
||||
path.join(brokenDir, "feature.manifest.ts"),
|
||||
`export const brokenManifest = { name: "broken" };`,
|
||||
);
|
||||
const manifests = findAllManifests(root);
|
||||
const unparseable = findUnparseableManifests(manifests);
|
||||
expect(unparseable.map((m) => m.feature)).toEqual(["broken"]);
|
||||
});
|
||||
|
||||
it("returns [] when every manifest parses", () => {
|
||||
const root = makeRepo({ auth: { signIn: {} } });
|
||||
expect(findUnparseableManifests(findAllManifests(root))).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildEventGraph + findOrphanConsumers", () => {
|
||||
it("finds zero orphans when consumers and publishers line up", () => {
|
||||
const root = makeRepo({
|
||||
@@ -68,7 +97,9 @@ describe("conformance script", () => {
|
||||
const orphans = findOrphanConsumers(graph);
|
||||
expect(orphans).toHaveLength(1);
|
||||
expect(orphans[0].event).toBe("auth.signed-up");
|
||||
expect(orphans[0].consumers).toEqual([{ feature: "marketing", useCase: "onAuthSignedUp" }]);
|
||||
expect(orphans[0].consumers).toEqual([
|
||||
{ feature: "marketing", useCase: "onAuthSignedUp" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("treats publish-only events as fine (no consumers is not an orphan)", () => {
|
||||
|
||||
Reference in New Issue
Block a user