feat(F-001): scaffold modular monolith skeleton with boundary checker
- TypeScript + Fastify skeleton under project/ (src/modules, shared, infrastructure, app) - scripts/check-module-boundaries.mjs enforcing module public-API rules (tested with fixtures) - GET /health endpoint, error envelope without stack leakage - specs/F-001-scaffold (SPEC/DESIGN/TASKS/TESTS), spec/tech.md dependency justification - 30-ticket MercadoDeVida roadmap in backlog/features.json, spec/roadmap.md - All gates approved: reviewer, security, qa; verify.sh green
This commit is contained in:
49
project/scripts/tests/boundary-checker.test.ts
Normal file
49
project/scripts/tests/boundary-checker.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
2
project/scripts/tests/fixtures/cross-module-internal/modules/alpha/api/routes.ts
vendored
Normal file
2
project/scripts/tests/fixtures/cross-module-internal/modules/alpha/api/routes.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import { secret } from '../../beta/domain/secret.js';
|
||||
export const routes = [secret];
|
||||
1
project/scripts/tests/fixtures/cross-module-internal/modules/beta/domain/secret.ts
vendored
Normal file
1
project/scripts/tests/fixtures/cross-module-internal/modules/beta/domain/secret.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export const secret = 'secret';
|
||||
2
project/scripts/tests/fixtures/deep-from-app/app/main.ts
vendored
Normal file
2
project/scripts/tests/fixtures/deep-from-app/app/main.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import { routes } from '../modules/alpha/api/routes.js';
|
||||
export const main = [routes];
|
||||
1
project/scripts/tests/fixtures/deep-from-app/modules/alpha/api/routes.ts
vendored
Normal file
1
project/scripts/tests/fixtures/deep-from-app/modules/alpha/api/routes.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export const routes = 'routes';
|
||||
3
project/scripts/tests/fixtures/ok/app/main.ts
vendored
Normal file
3
project/scripts/tests/fixtures/ok/app/main.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { alpha } from '../modules/alpha/index.js';
|
||||
import { beta } from '../modules/beta/index.js';
|
||||
export const main = [alpha, beta];
|
||||
3
project/scripts/tests/fixtures/ok/modules/alpha/api/routes.ts
vendored
Normal file
3
project/scripts/tests/fixtures/ok/modules/alpha/api/routes.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { alpha } from '../index.js';
|
||||
import { sharedUtil } from '../../../shared/util.js';
|
||||
export const routes = [alpha, sharedUtil];
|
||||
1
project/scripts/tests/fixtures/ok/modules/alpha/index.ts
vendored
Normal file
1
project/scripts/tests/fixtures/ok/modules/alpha/index.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export const alpha = 'alpha';
|
||||
1
project/scripts/tests/fixtures/ok/modules/beta/index.ts
vendored
Normal file
1
project/scripts/tests/fixtures/ok/modules/beta/index.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export const beta = 'beta';
|
||||
1
project/scripts/tests/fixtures/ok/shared/util.ts
vendored
Normal file
1
project/scripts/tests/fixtures/ok/shared/util.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export const sharedUtil = 'shared';
|
||||
Reference in New Issue
Block a user