// No-core-extension-imports test — locks in the [E00-S01-T14] rule that no // core package imports a concrete extension. // // Acceptance criteria covered (each test fails without the committed state): // - "no core package imports a concrete extension" -> the architecture // import test (E00-S01-T04, tests/architecture-import.test.mjs) is part // of the root test suite (the root `scripts.test` glob) and passes // against the real workspace graph with zero violations: no `packages/*` // source file imports anything that resolves to `extensions/*`. // - "the architecture import test catches any such import" -> the test // file is committed with the real-graph enforcement, the rule file // (dependency-boundaries.json) forbids the `packages -> extensions` // edge, and a mutation probe proves detection: in a temp workspace copy // with a core -> extension import injected into // packages/core/src/index.ts, the architecture import test exits // non-zero and reports the offending file and the // `packages -> extensions` rule. // // The passing real-graph probe is non-vacuous: the mutation probe proves the // architecture import test fails on an injected core -> extension import, so // a compliant result means the scanner really ran and found nothing. // // Run: `node --test tests/no-core-extension-imports.test.mjs` // (node:test — built into Node >= 18; no dependencies, lockfile untouched.) import test from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync, cpSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const read = (relPath) => readFileSync(path.join(REPO_ROOT, relPath), 'utf8'); /** The architecture import test named by the acceptance criterion (E00-S01-T04). */ const ARCH_TEST = 'tests/architecture-import.test.mjs'; /** The rule file that declares the forbidden packages -> extensions edge. */ const RULE_FILE = 'dependency-boundaries.json'; /** The root test glob (root `scripts.test`, E00-S01-T05/T12) that runs every suite. */ const ROOT_TEST_GLOB = 'tests/**/*.test.mjs'; /** The integration test inside the architecture import test that checks the real graph. */ const REAL_GRAPH_TEST = 'the architecture import test passes on the current compliant workspace graph'; /** A core -> extension import that must be caught by the architecture import test. */ const FORBIDDEN_IMPORT = "import { register } from '@personal-blog/example-extension';\n"; /** * Runs the architecture import test with the given cwd, using the same Node * runtime that runs this suite, and returns the spawn result. * * `NODE_TEST_CONTEXT` is stripped from the child env: when this suite runs * under `node --test`, that variable marks the process as a test-runner * child, and a nested `node --test` invocation would otherwise be treated as * a recursive run and skip its files. */ function runArchitectureTest(cwd) { const env = { ...process.env }; delete env.NODE_TEST_CONTEXT; return spawnSync(process.execPath, ['--test', ARCH_TEST], { cwd, env, encoding: 'utf8', timeout: 120_000, }); } /** True when the root test glob covers a repo-root-relative test path. */ function coveredByRootGlob(relPath) { // tests/**/*.test.mjs — "tests/" prefix plus "/*.test.mjs" at any depth. return relPath.startsWith('tests/') && relPath.endsWith('.test.mjs'); } /** A minimal package.json for a temp workspace package (only the name matters here). */ function tempManifest(name) { return `${JSON.stringify({ name, version: '0.0.0', private: true }, null, 2)}\n`; } /** * Builds a minimal but complete temp workspace copy — the rule file, the * architecture import test, and the three expected workspace packages * (apps/server, packages/core, extensions/example) — with * `packages/core/src/index.ts` set to `coreSource`. Returns the temp dir. */ function writeTempWorkspace({ coreSource }) { const dir = mkdtempSync(path.join(os.tmpdir(), 'eppp-core-ext-')); // The artifact under test plus the rule it enforces. cpSync(path.join(REPO_ROOT, RULE_FILE), path.join(dir, RULE_FILE)); mkdirSync(path.join(dir, 'tests'), { recursive: true }); cpSync(path.join(REPO_ROOT, ARCH_TEST), path.join(dir, ARCH_TEST)); // The three workspace packages the architecture test's integration case // expects to discover (apps/server, packages/core, extensions/example). const packages = { 'apps/server': { name: '@personal-blog/server', src: 'export {};\n' }, 'packages/core': { name: '@personal-blog/core', src: coreSource }, 'extensions/example': { name: '@personal-blog/example-extension', src: 'export {};\n' }, }; for (const [relDir, pkg] of Object.entries(packages)) { mkdirSync(path.join(dir, relDir, 'src'), { recursive: true }); writeFileSync(path.join(dir, relDir, 'package.json'), tempManifest(pkg.name)); writeFileSync(path.join(dir, relDir, 'src', 'index.ts'), pkg.src); } return dir; } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- test('the architecture import test is committed, wired into the root suite, and forbids the edge', () => { // The artifact named by the criterion exists and still carries the // real-graph enforcement ("no core package may import a concrete extension"). const archTest = read(ARCH_TEST); assert.match( archTest, /no core package may import a concrete extension/, `${ARCH_TEST} must contain the core -> extension enforcement message`, ); assert.match( archTest, new RegExp(REAL_GRAPH_TEST.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `${ARCH_TEST} must contain the real-workspace integration test "${REAL_GRAPH_TEST}"`, ); // It is picked up by the root test command (`pnpm test` -> the root // `scripts.test` glob), so a violation fails the whole root suite. const scripts = JSON.parse(read('package.json')).scripts ?? {}; assert.equal( scripts.test, `node --test "${ROOT_TEST_GLOB}"`, `root scripts.test must run the "${ROOT_TEST_GLOB}" glob so ${ARCH_TEST} runs with the rest of the suite`, ); assert.ok( coveredByRootGlob(ARCH_TEST), `${ARCH_TEST} must be covered by the root test glob "${ROOT_TEST_GLOB}"`, ); // The rule file forbids the packages -> extensions edge, so the // architecture import test has the forbidden edge to enforce. const rule = JSON.parse(read(RULE_FILE)); const forbidden = (rule.boundaries ?? []).filter((b) => !b.allow); assert.ok( forbidden.some((b) => b.from === 'packages' && b.to === 'extensions'), `${RULE_FILE} must forbid the packages -> extensions edge`, ); }); test('no core package imports a concrete extension (architecture import test passes on the real workspace)', () => { const result = runArchitectureTest(REPO_ROOT); assert.equal( result.status, 0, `the architecture import test must pass on the real workspace graph ` + `(a core package may not import a concrete extension):\n${result.stdout}${result.stderr}`, ); }); test('the architecture import test catches a core -> extension import (mutation probe)', () => { const dir = writeTempWorkspace({ coreSource: FORBIDDEN_IMPORT }); try { const result = runArchitectureTest(dir); assert.notEqual( result.status, 0, 'the architecture import test must fail when a core package imports a concrete extension', ); const output = `${result.stdout}\n${result.stderr}`; assert.match( output, /packages[\\/]core[\\/]src[\\/]index\.ts/, 'the violation report must name the offending core source file', ); assert.match( output, /violates packages -> extensions/, 'the violation report must cite the packages -> extensions rule', ); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('a compliant temp workspace passes the architecture import test (probe sanity)', () => { const dir = writeTempWorkspace({ coreSource: 'export {};\n' }); try { const result = runArchitectureTest(dir); assert.equal( result.status, 0, 'a temp workspace without core -> extension imports must pass the architecture import test ' + `(so the mutation probe failure is caused by the injected import):\n${result.stdout}${result.stderr}`, ); } finally { rmSync(dir, { recursive: true, force: true }); } });