Files
flights_web/tests/eslint/boundaries.test.ts
gnezim 8459e1661b Add fabricated violation tests for boundary rules
Fix eslint.config.js: add import/resolver settings so boundaries plugin
can resolve .ts/.tsx imports, and merge no-restricted-imports blocks to
prevent ESLint 9 flat config from overriding earlier rule definitions.
2026-04-14 23:06:57 +03:00

131 lines
4.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { execSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
const ROOT = path.resolve(import.meta.dirname, "../..");
/** Short random suffix to avoid collisions when tests run in parallel. */
function uid(): string {
return crypto.randomBytes(4).toString("hex");
}
/**
* Removes a file and any empty parent directories up to (but not including) the src/ tree root.
*/
function cleanupProbe(absPath: string): void {
try {
fs.unlinkSync(absPath);
} catch {
/* file may not exist */
}
let dir = path.dirname(absPath);
const srcDir = path.join(ROOT, "src");
while (dir.length > srcDir.length) {
try {
fs.rmdirSync(dir);
} catch {
break;
}
dir = path.dirname(dir);
}
}
/**
* Creates a probe source file, optionally a target file, runs ESLint, and cleans up.
*/
function lintProbe(
sourcePath: string,
sourceContent: string,
targetPath?: string,
targetContent?: string,
): string {
const absSrc = path.join(ROOT, sourcePath);
const absTarget = targetPath ? path.join(ROOT, targetPath) : null;
fs.mkdirSync(path.dirname(absSrc), { recursive: true });
fs.writeFileSync(absSrc, sourceContent, "utf8");
if (absTarget && targetContent) {
fs.mkdirSync(path.dirname(absTarget), { recursive: true });
fs.writeFileSync(absTarget, targetContent, "utf8");
}
try {
execSync(`pnpm exec eslint "${absSrc}" --format json`, {
cwd: ROOT,
encoding: "utf8",
stdio: ["pipe", "pipe", "pipe"],
});
return "PASS";
} catch (err: unknown) {
const error = err as { stdout?: string };
return error.stdout ?? "UNKNOWN_ERROR";
} finally {
cleanupProbe(absSrc);
if (absTarget) {
cleanupProbe(absTarget);
}
}
}
const TARGET_CONTENT = "export const something = 1;\n";
describe("boundaries rules", () => {
it("features/ cannot import from routes/", () => {
const id = uid();
const result = lintProbe(
`src/features/online-board/__bnd_probe_${id}__.ts`,
`import { something } from "../../routes/__probe_${id}__/__target_probe__";\nexport const x = something;\n`,
`src/routes/__probe_${id}__/__target_probe__.ts`,
TARGET_CONTENT,
);
expect(result).toContain("boundaries/element-types");
});
it("features/ cannot import from mf/", () => {
const id = uid();
const result = lintProbe(
`src/features/online-board/__bnd_probe_${id}__.ts`,
`import { something } from "../../mf/expose/__target_probe_${id}__";\nexport const x = something;\n`,
`src/mf/expose/__target_probe_${id}__.ts`,
TARGET_CONTENT,
);
expect(result).toContain("boundaries/element-types");
});
it("ui/ cannot import from features/", () => {
const id = uid();
const result = lintProbe(
`src/ui/__probe_${id}__/__bnd_probe__.ts`,
'import { something } from "../../features/online-board/index";\nexport const x = something;\n',
);
expect(result).toContain("boundaries/element-types");
});
it("shared/ cannot import from features/", () => {
const id = uid();
const result = lintProbe(
`src/shared/__probe_${id}__/__bnd_probe__.ts`,
'import { something } from "../../features/online-board/index";\nexport const x = something;\n',
);
expect(result).toContain("boundaries/element-types");
});
it("observability/ cannot import from features/", () => {
const id = uid();
const result = lintProbe(
`src/observability/logger/__bnd_probe_${id}__.ts`,
'import { something } from "../../features/online-board/index";\nexport const x = something;\n',
);
expect(result).toContain("boundaries/element-types");
});
it("features/ CAN import from env/ (allowed direction)", () => {
const id = uid();
const result = lintProbe(
`src/features/online-board/__bnd_probe_${id}__.ts`,
'import { getEnv } from "../../env/index";\nexport const x = getEnv;\n',
);
expect(result).not.toContain("boundaries/element-types");
});
});