test: verify housekeeping foundation boundaries

This commit is contained in:
Simo committed 2026-08-26 17:44:03 +02:00
1 parent a47b4eb195
commit a158c78a7c
3 files changed
+524 -3

No files matched your search

+2 -1
View File
@@ -22,7 +22,8 @@
"db:migrate": "tsx scripts/apply-migrations.ts",
"db:migrate:status": "tsx scripts/apply-migrations.ts --status",
"db:studio": "drizzle-kit studio",
"hk:matrix:check": "tsx scripts/verify-housekeeping-matrix.ts"
"hk:matrix:check": "tsx scripts/verify-housekeeping-matrix.ts",
"test:housekeeping": "vitest run --coverage.enabled=false src/features/housekeeping src/lib/admin-theme-source-audit.test.ts src/lib/admin/authorization-contract.test.ts"
},
"lint-staged": {
"*.{js,ts,jsx,tsx,json}": "biome check --write --no-errors-on-unmatched"
@@ -0,0 +1,513 @@
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { join, posix } from "node:path";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
import { discoverLegacyPages } from "../migration/discover-legacy-pages";
import { HOUSEKEEPING_MIGRATION_MATRIX } from "../migration/matrix";
import { validateMigrationEntries } from "../migration/validate-matrix";
import { isHousekeepingPreviewEnabled } from "./preview-gate";
import { createHousekeepingRegistry } from "./registry";
import { CommandTrigger } from "./shell/command-trigger";
const HOUSEKEEPING_ROOT = "src/features/housekeeping";
const SERVER_CAPABILITY_CONTEXT =
"src/features/housekeeping/foundation/server-capability-context.ts";
const PERMISSIONS_ADAPTER = "src/lib/permissions";
const DOMAIN_MODULE_ROOT = "src/features/housekeeping/domains";
const forbiddenModuleRoots = [
"src/lib/db",
"src/lib/db-pool",
"src/lib/cached-db",
"src/db",
"src/generated/prisma",
"src/actions",
"src/app/actions",
"src/lib/auth",
"src/app/admin",
"src/app/mod",
"@prisma/client",
"drizzle-orm",
"mysql2",
"cmdk",
] as const;
const resolverExtensionPattern = /\.(?:js|jsx|mjs|cjs|ts|tsx|mts|cts)$/i;
interface BabelNode {
type: string;
[key: string]: unknown;
}
interface BabelParser {
parse(
source: string,
options: {
createImportExpressions: boolean;
plugins: readonly ["typescript", "jsx"];
sourceType: "module";
},
): BabelNode;
}
interface ModuleAccessScan {
specifiers: readonly string[];
violations: readonly string[];
}
interface CanonicalModuleSpecifier {
candidates: readonly string[];
violation: string | null;
}
const projectRequire = createRequire(import.meta.url);
const requireFromVitest = createRequire(
projectRequire.resolve("vitest/package.json"),
);
const babelParser = requireFromVitest("@babel/parser") as BabelParser;
const expressionWrapperTypes = new Set([
"ParenthesizedExpression",
"TSAsExpression",
"TSInstantiationExpression",
"TSNonNullExpression",
"TSSatisfiesExpression",
"TSTypeAssertion",
"TypeCastExpression",
]);
function runtimeSourceFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true })
.flatMap((entry) => {
const path = join(directory, entry.name);
if (entry.isDirectory()) return runtimeSourceFiles(path);
if (
!/\.(?:ts|tsx)$/.test(entry.name) ||
entry.name.endsWith(".test.ts") ||
entry.name.endsWith(".test.tsx")
) {
return [];
}
return [path.replaceAll("\\", "/")];
})
.sort((a, b) => a.localeCompare(b));
}
function isBabelNode(value: unknown): value is BabelNode {
return (
typeof value === "object" &&
value !== null &&
"type" in value &&
typeof value.type === "string"
);
}
function unwrapModuleArgument(node: unknown): BabelNode | null {
let current = isBabelNode(node) ? node : null;
while (current && expressionWrapperTypes.has(current.type)) {
current = isBabelNode(current.expression) ? current.expression : null;
}
return current;
}
function readLiteralModuleSpecifier(node: unknown): string | null {
const literal = unwrapModuleArgument(node);
if (!literal) return null;
if (literal.type === "StringLiteral" && typeof literal.value === "string") {
return literal.value;
}
if (literal.type !== "TemplateLiteral") return null;
const expressions = Array.isArray(literal.expressions)
? literal.expressions
: [];
const quasis = Array.isArray(literal.quasis) ? literal.quasis : [];
if (expressions.length !== 0 || quasis.length !== 1) return null;
const quasi = isBabelNode(quasis[0]) ? quasis[0] : null;
const value = quasi?.value;
return typeof value === "object" &&
value !== null &&
"cooked" in value &&
typeof value.cooked === "string"
? value.cooked
: null;
}
function memberPropertyName(node: BabelNode): string | null {
const property = unwrapModuleArgument(node.property);
if (!property) return null;
if (node.computed === true) return readLiteralModuleSpecifier(property);
return property.type === "Identifier" && typeof property.name === "string"
? property.name
: null;
}
function isGuardedRequireCallee(node: unknown): boolean {
const callee = unwrapModuleArgument(node);
if (!callee) return false;
if (callee.type === "Identifier" && callee.name === "require") return true;
if (
callee.type !== "MemberExpression" &&
callee.type !== "OptionalMemberExpression"
) {
return false;
}
const object = unwrapModuleArgument(callee.object);
const property = memberPropertyName(callee);
return (
(object?.type === "Identifier" &&
object.name === "module" &&
property === "require") ||
(object?.type === "Identifier" &&
object.name === "require" &&
property === "resolve")
);
}
function isTypeOnlyDeclaration(
node: BabelNode,
kind: "importKind" | "exportKind",
): boolean {
if (node[kind] === "type" || node[kind] === "typeof") return true;
const specifiers = Array.isArray(node.specifiers) ? node.specifiers : [];
return (
specifiers.length > 0 &&
specifiers.every((specifier) => {
const declaration = isBabelNode(specifier) ? specifier : null;
return (
declaration?.importKind === "type" || declaration?.exportKind === "type"
);
})
);
}
function scanModuleAccesses(source: string): ModuleAccessScan {
const root = babelParser.parse(source, {
createImportExpressions: true,
plugins: ["typescript", "jsx"],
sourceType: "module",
});
const specifiers: string[] = [];
const violations: string[] = [];
function recordArgument(argument: unknown, kind: "import" | "require") {
const specifier = readLiteralModuleSpecifier(argument);
if (specifier === null) {
violations.push(`<non-literal ${kind}>`);
return;
}
specifiers.push(specifier);
}
function visit(value: unknown): void {
if (Array.isArray(value)) {
for (const item of value) visit(item);
return;
}
if (!isBabelNode(value)) return;
if (value.type === "ImportDeclaration") {
if (!isTypeOnlyDeclaration(value, "importKind")) {
const specifier = readLiteralModuleSpecifier(value.source);
if (specifier !== null) specifiers.push(specifier);
}
} else if (
(value.type === "ExportNamedDeclaration" ||
value.type === "ExportAllDeclaration") &&
value.source !== null
) {
if (!isTypeOnlyDeclaration(value, "exportKind")) {
const specifier = readLiteralModuleSpecifier(value.source);
if (specifier !== null) specifiers.push(specifier);
}
} else if (value.type === "ImportExpression") {
recordArgument(value.source, "import");
} else if (
value.type === "CallExpression" ||
value.type === "OptionalCallExpression"
) {
const arguments_ = Array.isArray(value.arguments) ? value.arguments : [];
if (isBabelNode(value.callee) && value.callee.type === "Import") {
recordArgument(arguments_[0], "import");
} else if (isGuardedRequireCallee(value.callee)) {
recordArgument(arguments_[0], "require");
}
} else if (value.type === "TSExternalModuleReference") {
recordArgument(value.expression, "require");
}
for (const [key, child] of Object.entries(value)) {
if (key !== "type") visit(child);
}
}
visit(root);
return { specifiers, violations };
}
function canonicalizeModuleSpecifier(
sourceFile: string,
specifier: string,
): CanonicalModuleSpecifier {
const suffixIndex = specifier.search(/[?#]/);
const withoutSuffix =
suffixIndex === -1 ? specifier : specifier.slice(0, suffixIndex);
let decoded: string;
try {
decoded = decodeURIComponent(withoutSuffix).replaceAll("\\", "/");
} catch {
return { candidates: [], violation: "<malformed module specifier>" };
}
let normalized: string;
if (decoded.startsWith("@/")) {
normalized = posix.normalize(`src/${decoded.slice(2)}`);
} else if (decoded.startsWith(".")) {
normalized = posix.normalize(
posix.join(posix.dirname(sourceFile), decoded),
);
} else {
normalized = posix.normalize(decoded);
}
return {
candidates: [
...new Set([
normalized,
normalized.replace(resolverExtensionPattern, ""),
]),
],
violation: null,
};
}
function isAtOrBelow(path: string, root: string): boolean {
return path === root || path.startsWith(`${root}/`);
}
function isDomainWorkflowModule(path: string): boolean {
if (!isAtOrBelow(path, DOMAIN_MODULE_ROOT)) return false;
return !/^src\/features\/housekeeping\/domains\/[^/]+\/manifest$/.test(path);
}
function isForbiddenModulePath(path: string, sourceFile: string): boolean {
if (isAtOrBelow(path, PERMISSIONS_ADAPTER)) {
return !(
sourceFile === SERVER_CAPABILITY_CONTEXT && path === PERMISSIONS_ADAPTER
);
}
return (
forbiddenModuleRoots.some((root) => isAtOrBelow(path, root)) ||
isDomainWorkflowModule(path)
);
}
function findHousekeepingImportBoundaryViolations(
source: string,
sourceFile: string,
): readonly string[] {
const scan = scanModuleAccesses(source);
const violations = [...scan.violations];
for (const specifier of scan.specifiers) {
const canonical = canonicalizeModuleSpecifier(sourceFile, specifier);
if (canonical.violation) violations.push(canonical.violation);
const forbiddenPath = canonical.candidates.find((candidate) =>
isForbiddenModulePath(candidate, sourceFile),
);
if (forbiddenPath) violations.push(forbiddenPath);
}
return violations;
}
describe("housekeeping runtime import boundary", () => {
it("keeps every runtime module inside the foundation boundary", () => {
for (const sourceFile of runtimeSourceFiles(HOUSEKEEPING_ROOT)) {
const source = readFileSync(sourceFile, "utf8");
expect(
findHousekeepingImportBoundaryViolations(source, sourceFile),
sourceFile,
).toEqual([]);
}
});
it.each([
[
"aliased database import",
"src/features/housekeeping/foundation/registry.ts",
'import { db } from "@/lib/db";',
"src/lib/db",
],
[
"relative action import",
"src/features/housekeeping/foundation/registry.ts",
'import action from "../../../actions/users";',
"src/actions/users",
],
[
"direct auth export",
"src/features/housekeeping/foundation/registry.ts",
'export * from "@/lib/auth";',
"src/lib/auth",
],
[
"legacy route import",
"src/features/housekeeping/foundation/registry.ts",
'import page from "../../../app/admin/users/page";',
"src/app/admin/users/page",
],
[
"command package import",
"src/features/housekeeping/foundation/registry.ts",
'import { Command } from "cmdk";',
"cmdk",
],
[
"domain workflow import",
"src/features/housekeeping/foundation/registry.ts",
'import workflow from "../domains/people/workflow";',
"src/features/housekeeping/domains/people/workflow",
],
] as const)("detects %s", (_name, sourceFile, source, expectedPath) => {
expect(
findHousekeepingImportBoundaryViolations(source, sourceFile),
).toContain(expectedPath);
});
it.each([
[
"dynamic import",
"const modulePath = '@/lib/db'; import(modulePath);",
"<non-literal import>",
],
[
"CommonJS require",
"const modulePath = '@/actions/users'; require(modulePath);",
"<non-literal require>",
],
] as const)(
"fails closed for a non-literal %s",
(_name, source, expected) => {
expect(
findHousekeepingImportBoundaryViolations(
source,
"src/features/housekeeping/foundation/registry.ts",
),
).toContain(expected);
},
);
it("allows only the server capability adapter to import permissions", () => {
const runtimeSource =
'import { getAdminContext } from "@/lib/permissions";';
const typeOnlySource =
'import type { PermissionSet } from "@/lib/permissions";';
expect(
findHousekeepingImportBoundaryViolations(
typeOnlySource,
"src/features/housekeeping/foundation/capability-context.ts",
),
).toEqual([]);
expect(
findHousekeepingImportBoundaryViolations(
runtimeSource,
SERVER_CAPABILITY_CONTEXT,
),
).toEqual([]);
expect(
findHousekeepingImportBoundaryViolations(
runtimeSource,
"src/features/housekeeping/foundation/capability-context.ts",
),
).toContain(PERMISSIONS_ADAPTER);
expect(
findHousekeepingImportBoundaryViolations(
'import adapter from "@/lib/permissions/internal";',
SERVER_CAPABILITY_CONTEXT,
),
).toContain("src/lib/permissions/internal");
});
it("allows harmless lookalikes and the declared domain manifests", () => {
const source = [
'import database from "@/lib/database";',
'import authentication from "@/lib/authentication";',
'import commandKit from "cmdkit";',
'import manifest from "../domains/people/manifest";',
"const documentation = \"import db from '@/lib/db'\";",
'// import action from "@/actions/users";',
].join("\n");
expect(
findHousekeepingImportBoundaryViolations(
source,
"src/features/housekeeping/manifests.ts",
),
).toEqual([]);
});
});
describe("housekeeping foundation completion contracts", () => {
it("leaves the current and preview route entrypoints present", () => {
for (const path of [
"src/app/admin/layout.tsx",
"src/app/mod/layout.tsx",
"src/app/admin-next/layout.tsx",
]) {
expect(existsSync(path), path).toBe(true);
}
});
it("creates the real six-domain registry in locked order without workflows", () => {
const registry = createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS);
expect(registry.domains.map((domain) => domain.id)).toEqual([
"operations",
"people",
"content",
"economy",
"hotel",
"system",
]);
expect(registry.domains.every((domain) => domain.routes.length === 0)).toBe(
true,
);
});
it("keeps production preview disabled even when the flag is true", () => {
expect(
isHousekeepingPreviewEnabled({ nodeEnv: "production", flag: true }),
).toBe(false);
});
it("renders one inert localized command affordance", () => {
const sentinel = "HK::comando-localizzato-disabilitato";
const html = renderToStaticMarkup(
createElement(CommandTrigger, { label: sentinel }),
);
const buttons = html.match(/<button\b[^>]*>/g) ?? [];
expect(buttons).toHaveLength(1);
expect(buttons[0]).toMatch(/\sdisabled(?:=""|(?=\s|>))/);
expect(buttons[0]).not.toMatch(/\son[a-z][a-z0-9-]*\s*=/i);
expect(html).toContain(`>${sentinel}</button>`);
});
it("validates the complete 137-row migration matrix without issues", () => {
const discovered = discoverLegacyPages();
const issues = validateMigrationEntries(
discovered,
HOUSEKEEPING_MIGRATION_MATRIX,
);
expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(137);
expect(discovered).toHaveLength(137);
expect(issues).toEqual([]);
});
});
+9 -2
View File
@@ -2,7 +2,12 @@ import { readdirSync, readFileSync } from "node:fs";
import { join, relative } from "node:path";
import { describe, expect, it } from "vitest";
const ROOTS = ["src/app/admin", "src/components/admin"];
const ROOTS = [
"src/app/admin",
"src/components/admin",
"src/app/admin-next",
"src/features/housekeeping",
];
const GRAPHICAL_ALLOWLIST = [
"src/app/admin/favicon/favicon-generator.tsx",
"src/app/admin/import/clone/import-clone-client.tsx",
@@ -34,7 +39,9 @@ function sourceFiles(directory: string): string[] {
const path = join(directory, entry.name);
return entry.isDirectory()
? sourceFiles(path)
: /\.(?:ts|tsx)$/.test(entry.name)
: /\.(?:ts|tsx)$/.test(entry.name) &&
!entry.name.endsWith(".test.ts") &&
!entry.name.endsWith(".test.tsx")
? [path]
: [];
});