import { execFile } from 'node:child_process'; import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; const here = path.dirname(fileURLToPath(import.meta.url)); const scriptPath = path.resolve(here, '..', 'check-module-boundaries.mjs'); const fixturesDir = path.resolve(here, 'fixtures'); function runChecker(rootDir: string): Promise<{ code: number; output: string }> { return new Promise((resolve) => { execFile( process.execPath, [scriptPath, rootDir], { encoding: 'utf8' }, (error, stdout, stderr) => { const code = error !== null && 'code' in error ? (error.code as number) : 0; resolve({ code, output: `${stdout}\n${stderr}` }); }, ); }); } describe('check-module-boundaries', () => { it('passes on a clean tree', async () => { const { code, output } = await runChecker(path.join(fixturesDir, 'ok')); expect(output).toContain('Boundary check OK'); expect(code).toBe(0); }); it('fails when a module imports another module internal file (R1)', async () => { const { code, output } = await runChecker(path.join(fixturesDir, 'cross-module-internal')); expect(output).toContain('R1 violation'); expect(code).toBe(1); }); it('fails when outside code deep-imports a module internal file (R2)', async () => { const { code, output } = await runChecker(path.join(fixturesDir, 'deep-from-app')); expect(output).toContain('R2 violation'); expect(code).toBe(1); }); it('allows outside code to import a module index (R2 ok)', async () => { const { code, output } = await runChecker(path.join(fixturesDir, 'ok')); expect(output).toContain('Boundary check OK'); expect(code).toBe(0); }); });