import { readdirSync, readFileSync, statSync } from "node:fs"; import { join, resolve } from "node:path"; import { describe, expect, it } from "@suaveplan/testing/runner"; const pkgRoot = resolve(__dirname, ".."); const srcRoot = join(pkgRoot, "src"); function walk(dir: string): string[] { const out: string[] = []; for (const entry of readdirSync(dir)) { const full = join(dir, entry); const st = statSync(full); if (st.isDirectory()) out.push(...walk(full)); else out.push(full); } return out; } describe("@suaveplan/api-plugin-auth dep graph + layout", () => { it("package.json carries required dependencies", { timeout: 5000 }, () => { const pkg = JSON.parse( readFileSync(join(pkgRoot, "package.json"), "utf8") ) as { dependencies: Record; devDependencies: Record; }; const deps = pkg.dependencies; expect(deps["@suaveplan/api-plugin"]).toBe("workspace:^"); expect(deps["@suaveplan/api-openapi"]).toBe("workspace:^"); expect(deps["@suaveplan/api-migrations"]).toBe("workspace:^"); expect(deps["@suaveplan/better-auth-surrealdb"]).toBe("workspace:^"); expect(deps["@suaveplan/surrealdb"]).toBe("workspace:^"); expect(deps["@suaveplan/error"]).toBe("workspace:^"); expect(deps["@suaveplan/types"]).toBe("workspace:^"); expect(deps["hono"]).toBe("catalog:"); expect(deps["@hono/zod-openapi"]).toBe("catalog:server"); expect(deps["zod"]).toBe("catalog:forms"); expect(deps["better-auth"]).toBe("catalog:auth"); expect(deps["@opentelemetry/api"]).toBe("catalog:observability"); }); it("package.json exposes 15 subpath exports", { timeout: 5000 }, () => { const pkg = JSON.parse( readFileSync(join(pkgRoot, "package.json"), "utf8") ) as { exports: Record }; const subs = Object.keys(pkg.exports).filter( (k) => k.startsWith("./") && k !== "./package.json" ); // 15 named feature subpaths (root "." is excluded by filter). expect(subs.length).toBe(15); }); it("every impl .ts has a sibling .md", { timeout: 5000 }, () => { const missing: string[] = []; for (const file of walk(srcRoot)) { if (!file.endsWith(".ts")) continue; if ( file.endsWith(".test.ts") || file.endsWith(".spec.ts") || file.endsWith(".types.ts") || file.endsWith("/index.ts") ) { continue; } const md = file.replace(/\.ts$/, ".md"); try { statSync(md); } catch { missing.push(md); } } expect(missing).toEqual([]); }); it("every schemas/routes/.ts has a sibling .md", { timeout: 5000, }, () => { const routesDir = join(srcRoot, "schemas", "routes"); const missing: string[] = []; for (const entry of readdirSync(routesDir)) { if (!entry.endsWith(".ts")) continue; if ( entry === "index.ts" || entry.endsWith(".test.ts") || entry.endsWith(".types.ts") ) { continue; } const md = join(routesDir, entry.replace(/\.ts$/, ".md")); try { statSync(md); } catch { missing.push(md); } } expect(missing).toEqual([]); }); it("README.md is present at package root", { timeout: 5000 }, () => { const readme = readFileSync(join(pkgRoot, "README.md"), "utf8"); expect(readme.length).toBeGreaterThan(200); expect(readme).toContain("@suaveplan/api-plugin-auth"); }); it("every subpath export resolves to a file within src/", { timeout: 5000, }, () => { const pkg = JSON.parse( readFileSync(join(pkgRoot, "package.json"), "utf8") ) as { exports: Record; }; const missing: string[] = []; for (const [sub, val] of Object.entries(pkg.exports)) { if (sub === "./package.json") continue; const defaultPath = typeof val === "string" ? val : (val?.import?.default ?? undefined); if (defaultPath === undefined) continue; // dist/.js → src/.ts const rel = defaultPath .replace(/^\.\/dist\//, "") .replace(/\.js$/, ".ts"); const srcPath = join(srcRoot, rel); try { statSync(srcPath); } catch { missing.push(`${sub} → ${srcPath}`); } } expect(missing).toEqual([]); }); });