murashit_codex-panel/tests/app-server/generated-import-boundary.test.ts

63 lines
2.7 KiB
TypeScript

import { readdir, readFile } from "node:fs/promises";
import path from "node:path";
import { describe, expect, it } from "vitest";
const repoRoot = process.cwd();
const sourceRoot = path.join(repoRoot, "src");
const testsRoot = path.join(repoRoot, "tests");
const generatedImportPattern = /(?:from\s+|import\s*\(\s*|require\s*\(\s*)["'][^"']*generated\/app-server\//;
const sourceExtensions = new Set([".ts", ".tsx"]);
describe("generated app-server import boundary", () => {
it("detects static, import type, and re-export generated app-server references", () => {
expect(generatedImportPattern.test('import type { Thread } from "../../src/generated/app-server/v2/Thread";')).toBe(true);
expect(generatedImportPattern.test('type Thread = import("../../src/generated/app-server/v2/Thread").Thread;')).toBe(true);
expect(generatedImportPattern.test('const thread = require("../../src/generated/app-server/v2/Thread");')).toBe(true);
expect(generatedImportPattern.test('export type { Thread } from "../../src/generated/app-server/v2/Thread";')).toBe(true);
expect(generatedImportPattern.test('export * from "../../src/generated/app-server/v2/Thread";')).toBe(true);
});
it("keeps generated app-server types behind the app-server boundary", async () => {
const files = await sourceFiles(sourceRoot);
const offenders: string[] = [];
for (const file of files) {
const relativePath = slashPath(path.relative(repoRoot, file));
if (relativePath.startsWith("src/generated/")) continue;
if (relativePath.startsWith("src/app-server/")) continue;
if (generatedImportPattern.test(await readFile(file, "utf8"))) offenders.push(relativePath);
}
expect(offenders).toEqual([]);
});
it("keeps generated app-server types out of feature tests", async () => {
const files = await sourceFiles(testsRoot);
const offenders: string[] = [];
for (const file of files) {
const relativePath = slashPath(path.relative(repoRoot, file));
if (relativePath.startsWith("tests/app-server/")) continue;
if (generatedImportPattern.test(await readFile(file, "utf8"))) offenders.push(relativePath);
}
expect(offenders).toEqual([]);
});
});
async function sourceFiles(directory: string): Promise<string[]> {
const entries = await readdir(directory, { withFileTypes: true });
const files = await Promise.all(
entries.map(async (entry) => {
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) return sourceFiles(fullPath);
if (entry.isFile() && sourceExtensions.has(path.extname(entry.name))) return [fullPath];
return [];
}),
);
return files.flat();
}
function slashPath(value: string): string {
return value.split(path.sep).join("/");
}