Initial Commit
This commit is contained in:
179
packages/api-stack/api/api-tenant/tests/dep-graph.test.ts
Normal file
179
packages/api-stack/api/api-tenant/tests/dep-graph.test.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
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-plugin",
|
||||
"@suaveplan/error",
|
||||
"@suaveplan/events",
|
||||
"@suaveplan/types",
|
||||
"hono",
|
||||
"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 server allowlist", { timeout: 5000 }, () => {
|
||||
const deps = Object.keys(pkg.dependencies ?? {});
|
||||
for (const d of deps) {
|
||||
expect(ALLOWED_DEPS.has(d)).toBe(true);
|
||||
}
|
||||
// Every allowed dep is present (no skip-by-omission).
|
||||
for (const expected of [
|
||||
"@suaveplan/error",
|
||||
"@suaveplan/api-plugin",
|
||||
"@suaveplan/events",
|
||||
"@suaveplan/types",
|
||||
"hono",
|
||||
"tslib",
|
||||
"zod",
|
||||
"@opentelemetry/api",
|
||||
]) {
|
||||
expect(deps).toContain(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("subpath exports cover every feature", { timeout: 5000 }, () => {
|
||||
const exports = pkg.exports ?? {};
|
||||
const expected = [
|
||||
".",
|
||||
"./error-codes",
|
||||
"./types/tenant",
|
||||
"./resolver",
|
||||
"./cache",
|
||||
"./context",
|
||||
"./middleware",
|
||||
"./rate-limit",
|
||||
"./lifecycle",
|
||||
"./storage",
|
||||
];
|
||||
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(".types.ts")) continue;
|
||||
const base = file.replace(/\.ts$/, "");
|
||||
const fileName = base.split("/").pop();
|
||||
if (fileName === "index") continue;
|
||||
// The root index.ts is covered by the package README.
|
||||
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([]);
|
||||
});
|
||||
});
|
||||
|
||||
// Guard against accidental flat-error-codes file showing up.
|
||||
describe("layout invariants", () => {
|
||||
it("error-codes lives at src/error-codes/error-codes.ts (directory form)", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const inner = join(PKG_DIR, "src", "error-codes", "error-codes.ts");
|
||||
statSync(inner);
|
||||
const dir = dirname(inner);
|
||||
// No flat src/error-codes.ts collision.
|
||||
const flat = join(PKG_DIR, "src", "error-codes.ts");
|
||||
let flatExists = true;
|
||||
try {
|
||||
statSync(flat);
|
||||
} catch {
|
||||
flatExists = false;
|
||||
}
|
||||
expect(flatExists).toBe(false);
|
||||
expect(dir.endsWith("/error-codes")).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user