Files
EpicNext-Cms/src/features/housekeeping/cutover/source-boundaries.test.ts
T

94 lines
2.9 KiB
TypeScript

import { readdirSync, readFileSync } from "node:fs";
import { dirname, relative, resolve, sep } from "node:path";
import { describe, expect, it } from "vitest";
const housekeepingRoot = resolve(process.cwd(), "src/features/housekeeping");
function sourceFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = resolve(directory, entry.name);
if (entry.isDirectory()) return sourceFiles(path);
if (!/\.(?:ts|tsx)$/u.test(entry.name) || /\.test\./u.test(entry.name)) {
return [];
}
return [path];
});
}
function moduleSpecifiers(source: string): string[] {
return [
...source.matchAll(
/(?:from\s+|import\s*\(|import\s+|require\s*\()\s*["']([^"']+)["']/gu,
),
].flatMap((match) => (match[1] ? [match[1]] : []));
}
function resolveHousekeepingImport(
file: string,
specifier: string,
): string | null {
if (specifier.startsWith(".")) {
return resolve(dirname(file), specifier).replaceAll("\\", "/");
}
const prefix = "@/features/housekeeping/";
if (specifier.startsWith(prefix)) {
return resolve(housekeepingRoot, specifier.slice(prefix.length)).replaceAll(
"\\",
"/",
);
}
return null;
}
function displayPath(path: string): string {
return relative(process.cwd(), path).split(sep).join("/");
}
describe("Housekeeping source boundaries", () => {
it("keeps foundation independent from domain internals, database, and actions", () => {
const violations = sourceFiles(resolve(housekeepingRoot, "foundation"))
.flatMap((file) =>
moduleSpecifiers(readFileSync(file, "utf8")).flatMap((specifier) => {
const resolved = resolveHousekeepingImport(file, specifier);
const importsDomain =
resolved?.includes("/features/housekeeping/domains/") === true;
const importsInfrastructure =
specifier === "@/db" ||
specifier.startsWith("@/db/") ||
specifier === "@/actions" ||
specifier.startsWith("@/actions/");
return importsDomain || importsInfrastructure
? [`${displayPath(file)} -> ${specifier}`]
: [];
}),
)
.sort();
expect(violations).toEqual([]);
});
it("prevents one domain from importing another domain's internals", () => {
const domainsRoot = resolve(housekeepingRoot, "domains");
const violations = sourceFiles(domainsRoot)
.flatMap((file) => {
const owner = relative(domainsRoot, file).split(sep)[0];
return moduleSpecifiers(readFileSync(file, "utf8")).flatMap(
(specifier) => {
const resolved = resolveHousekeepingImport(file, specifier);
if (!resolved) return [];
const marker = "/features/housekeeping/domains/";
const offset = resolved.indexOf(marker);
if (offset < 0) return [];
const target = resolved.slice(offset + marker.length).split("/")[0];
return target && target !== owner
? [`${displayPath(file)} -> ${specifier}`]
: [];
},
);
})
.sort();
expect(violations).toEqual([]);
});
});