Files
suaveplan/packages/api-stack/api/api-rbac/tests/dep-graph.test.ts
2026-05-27 23:29:53 +01:00

162 lines
5.2 KiB
TypeScript

import { readdirSync, readFileSync, statSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { describe, expect, it } from "@suaveplan/testing/runner";
const PKG_DIR = resolve(__dirname, "..");
const pkg = JSON.parse(readFileSync(join(PKG_DIR, "package.json"), "utf8")) as {
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
exports?: Record<string, unknown>;
};
const ALLOWED_DEPS = new Set([
"@opentelemetry/api",
"@suaveplan/api-types",
"@suaveplan/error",
"@suaveplan/logger",
"@suaveplan/rules",
"tslib",
"zod",
]);
function walk(dir: string, out: string[] = []): string[] {
for (const name of readdirSync(dir)) {
const p = join(dir, name);
const st = statSync(p);
if (st.isDirectory()) {
if (name === "node_modules" || name === "dist") continue;
walk(p, out);
} else {
out.push(p);
}
}
return out;
}
describe("dep-graph", () => {
it("dependencies match the allowlist", { timeout: 5000 }, () => {
const deps = Object.keys(pkg.dependencies ?? {});
for (const d of deps) {
expect(ALLOWED_DEPS.has(d)).toBe(true);
}
for (const expected of [
"@opentelemetry/api",
"@suaveplan/api-types",
"@suaveplan/error",
"@suaveplan/logger",
"@suaveplan/rules",
"tslib",
"zod",
]) {
expect(deps).toContain(expected);
}
});
it("subpath exports cover every feature", { timeout: 5000 }, () => {
const exports = pkg.exports ?? {};
const expected = [
".",
"./middleware",
"./registry",
"./spec",
"./error-codes",
"./instruments",
];
for (const e of expected) {
expect(Object.keys(exports)).toContain(e);
}
});
it("every impl .ts has a sibling .md", { timeout: 5000 }, () => {
const srcDir = join(PKG_DIR, "src");
const all = walk(srcDir);
const missing: string[] = [];
for (const file of all) {
if (!file.endsWith(".ts")) continue;
if (file.endsWith(".test.ts")) continue;
if (file.endsWith(".spec.ts")) continue;
if (file.endsWith(".spec-conformance.test.ts")) continue;
if (file.endsWith(".types.ts")) continue;
const base = file.replace(/\.ts$/, "");
const fileName = base.split("/").pop();
if (fileName === "index") continue;
if (file === join(srcDir, "index.ts")) continue;
const md = `${base}.md`;
try {
statSync(md);
} catch {
missing.push(md.replace(`${PKG_DIR}/`, ""));
}
}
expect(missing).toEqual([]);
});
it("no biome suppressions in src/", { timeout: 5000 }, () => {
const all = walk(join(PKG_DIR, "src"));
const hits: string[] = [];
for (const f of all) {
if (!f.endsWith(".ts")) continue;
if (f.endsWith(".test.ts")) continue;
const txt = readFileSync(f, "utf8");
if (txt.includes("biome-ignore")) hits.push(f);
if (txt.includes("eslint-disable")) hits.push(f);
}
expect(hits).toEqual([]);
});
it("no banned patterns in src/", { timeout: 5000 }, () => {
const banned = [
"TODO",
"FIXME",
"ts-ignore",
"ts-expect-error",
"@deprecated",
"console.log",
];
const all = walk(join(PKG_DIR, "src"));
const hits: { file: string; pattern: string }[] = [];
for (const f of all) {
if (!f.endsWith(".ts")) continue;
if (f.endsWith(".test.ts")) continue;
const txt = readFileSync(f, "utf8");
for (const p of banned) {
if (txt.includes(p)) hits.push({ file: f, pattern: p });
}
}
expect(hits).toEqual([]);
});
it("no `export *` in feature barrels", { timeout: 5000 }, () => {
const featureBarrels = readdirSync(join(PKG_DIR, "src"))
.filter((entry) => {
const p = join(PKG_DIR, "src", entry);
try {
return statSync(p).isDirectory();
} catch {
return false;
}
})
.map((d) => join(PKG_DIR, "src", d, "index.ts"));
const hits: string[] = [];
for (const b of featureBarrels) {
try {
const txt = readFileSync(b, "utf8");
if (/^export\s+\*/m.test(txt)) hits.push(b);
} catch {
/* directory may have no index.ts */
}
}
expect(hits).toEqual([]);
});
});
describe("layout invariants", () => {
it("error-codes lives at src/error-codes/error-codes.ts", {
timeout: 5000,
}, () => {
const inner = join(PKG_DIR, "src", "error-codes", "error-codes.ts");
statSync(inner);
expect(dirname(inner).endsWith("/error-codes")).toBe(true);
});
});