test: lock in pg/Kysely import isolation to database-postgres (E00-S03-T02)
CI / Frozen lockfile install (pull_request) Successful in 43s
CI / Secrets not embedded (E00-S02-T08) (pull_request) Successful in 30s
CI / Database-postgres import isolation (E00-S03-T02) (pull_request) Successful in 31s
CI / Compose config (E00-S03-T01) (pull_request) Successful in 24s
CI / Frozen lockfile install (pull_request) Successful in 43s
CI / Secrets not embedded (E00-S02-T08) (pull_request) Successful in 30s
CI / Database-postgres import isolation (E00-S03-T02) (pull_request) Successful in 31s
CI / Compose config (E00-S03-T01) (pull_request) Successful in 24s
- database-postgres-imports.test.mjs: static scan of every workspace package source proves pg/kysely import specifiers resolve only to packages/database-postgres; owner manifest pins the driver and no other package declares it; mutation probes prove the scan catches a driver import injected into apps/server/src/index.ts; comment-stripping and specifier matcher unit probes; CI-enforcement assertion - workspace-layout / workspace-config / strict-tsconfig / typescript-pin: package-set fixtures updated to include packages/database-postgres - docs/development/non-container.md: workspace package table and build expectations updated for the new package
This commit is contained in:
@@ -0,0 +1,490 @@
|
||||
/**
|
||||
* Database-postgres import isolation test — locks in the [E00-S03-T02] rule
|
||||
* that `pg`/Kysely imports live only in the `database-postgres` package.
|
||||
*
|
||||
* Acceptance criteria covered (each test fails without the committed state):
|
||||
* - "pg/Kysely imports are isolated to database-postgres" -> the workspace
|
||||
* package `packages/database-postgres` (`@personal-blog/database-postgres`)
|
||||
* exists, declares the driver as its own dependencies (`pg`, `kysely`
|
||||
* exact pins, `@types/pg` for types) and its source really imports the
|
||||
* driver; a static scan of every workspace package's source proves that
|
||||
* every `pg`/`kysely` import specifier (static import/export-from, dynamic
|
||||
* import, require) resolves to a file inside `packages/database-postgres`
|
||||
* — and no other package's manifest declares the driver either.
|
||||
* - "no other package imports the database driver directly" -> the same
|
||||
* scan finds zero driver imports outside the owner package, and the
|
||||
* committed state is non-vacuous: a mutation probe injects a `pg` import
|
||||
* into `apps/server/src/index.ts` in a temp copy of the committed tree
|
||||
* and the scan fails, naming the offending file and the driver; a clean
|
||||
* copy passes (probe sanity).
|
||||
*
|
||||
* Run: `node --test tests/database-postgres-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, readdirSync, writeFileSync, mkdirSync, cpSync, mkdtempSync, rmSync } from 'node:fs';
|
||||
import { readdir } from 'node:fs/promises';
|
||||
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 workspace package allowed to import the PostgreSQL driver (E00-S03-T02). */
|
||||
const OWNER_DIR = 'packages/database-postgres';
|
||||
|
||||
/** The root test glob (root `scripts.test`, E00-S01-T12) that runs every suite. */
|
||||
const ROOT_TEST_GLOB = 'tests/**/*.test.mjs';
|
||||
|
||||
/** The CI job that gates the isolation criterion on every PR. */
|
||||
const CI_JOB = 'database-postgres-imports';
|
||||
|
||||
/** Directories never scanned as package source. */
|
||||
const IGNORED_DIRS = new Set(['node_modules', 'dist', 'coverage', '.git', '.pnpm-store']);
|
||||
|
||||
/** The workspace groups scanned for source files (mirrors pnpm-workspace.yaml). */
|
||||
const GROUP_DIRS = ['apps', 'packages', 'extensions'];
|
||||
|
||||
/** Extensions scanned as package source. */
|
||||
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs', '.cjs']);
|
||||
|
||||
/**
|
||||
* True when a module specifier names the PostgreSQL driver: the `pg` package
|
||||
* or Kysely (including their subpaths, e.g. `pg/connection-string` or
|
||||
* `kysely/plugin/dialect`). `@types/pg` is a types-only package — never
|
||||
* imported at runtime — so it is not a driver import.
|
||||
*/
|
||||
function isDriverSpecifier(specifier) {
|
||||
return (
|
||||
specifier === 'pg' ||
|
||||
specifier.startsWith('pg/') ||
|
||||
specifier === 'kysely' ||
|
||||
specifier.startsWith('kysely/')
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Comment stripping (string/comment aware, preserves line count)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Replaces comments with whitespace while preserving line structure, so
|
||||
* specifier extraction never fires on commented-out imports and reported
|
||||
* line numbers still match the original source. Mirrors the stripper in
|
||||
* tests/architecture-import.test.mjs.
|
||||
*/
|
||||
function stripComments(source) {
|
||||
let out = '';
|
||||
let i = 0;
|
||||
const n = source.length;
|
||||
let state = 'code'; // code | line | block | sq | dq | tpl
|
||||
const tplStack = []; // template states to resume after `${...}` closes
|
||||
let braceDepth = 0;
|
||||
|
||||
while (i < n) {
|
||||
const c = source[i];
|
||||
const next = source[i + 1];
|
||||
|
||||
if (state === 'code') {
|
||||
if (tplStack.length > 0) {
|
||||
if (c === '{') {
|
||||
braceDepth += 1;
|
||||
out += c;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (c === '}') {
|
||||
braceDepth -= 1;
|
||||
out += c;
|
||||
i += 1;
|
||||
if (braceDepth === 0) state = tplStack.pop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (c === '/' && next === '/') {
|
||||
out += ' ';
|
||||
i += 2;
|
||||
state = 'line';
|
||||
continue;
|
||||
}
|
||||
if (c === '/' && next === '*') {
|
||||
out += ' ';
|
||||
i += 2;
|
||||
state = 'block';
|
||||
continue;
|
||||
}
|
||||
if (c === "'") {
|
||||
out += c;
|
||||
i += 1;
|
||||
state = 'sq';
|
||||
continue;
|
||||
}
|
||||
if (c === '"') {
|
||||
out += c;
|
||||
i += 1;
|
||||
state = 'dq';
|
||||
continue;
|
||||
}
|
||||
if (c === '`') {
|
||||
out += c;
|
||||
i += 1;
|
||||
state = 'tpl';
|
||||
continue;
|
||||
}
|
||||
out += c;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state === 'line') {
|
||||
if (c === '\n') {
|
||||
out += c;
|
||||
i += 1;
|
||||
state = 'code';
|
||||
} else {
|
||||
out += ' ';
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state === 'block') {
|
||||
if (c === '*' && next === '/') {
|
||||
out += ' ';
|
||||
i += 2;
|
||||
state = 'code';
|
||||
} else {
|
||||
out += c === '\n' ? c : ' ';
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state === 'sq' || state === 'dq') {
|
||||
const quote = state === 'sq' ? "'" : '"';
|
||||
out += c;
|
||||
if (c === '\\' && next !== undefined) {
|
||||
out += next;
|
||||
i += 2;
|
||||
} else {
|
||||
if (c === quote) state = 'code';
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// template literal
|
||||
out += c;
|
||||
if (c === '\\' && next !== undefined) {
|
||||
out += next;
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if (c === '`') {
|
||||
state = 'code';
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (c === '$' && next === '{') {
|
||||
out += next;
|
||||
i += 2;
|
||||
tplStack.push('tpl');
|
||||
braceDepth = 1;
|
||||
state = 'code';
|
||||
continue;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Driver-import discovery
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const STATIC_IMPORT = /\b(?:import|export)\s+(?:type\s+)?(?:[\w*{},\s]*?\s+from\s*)?(['"])([^'"]+)\1/g;
|
||||
const REQUIRE_CALL = /\brequire\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
||||
const DYNAMIC_IMPORT = /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
||||
|
||||
/** Returns [{ specifier, index }] for every module specifier in the (stripped) source. */
|
||||
function extractSpecifiers(stripped) {
|
||||
const found = [];
|
||||
for (const re of [STATIC_IMPORT, REQUIRE_CALL, DYNAMIC_IMPORT]) {
|
||||
re.lastIndex = 0;
|
||||
let m;
|
||||
while ((m = re.exec(stripped)) !== null) {
|
||||
found.push({ specifier: m[2], index: m.index });
|
||||
}
|
||||
}
|
||||
found.sort((a, b) => a.index - b.index);
|
||||
return found;
|
||||
}
|
||||
|
||||
function lineOf(stripped, index) {
|
||||
return stripped.slice(0, index).split('\n').length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns [{ relPath, line, specifier }] for every pg/Kysely import found in
|
||||
* the given source files. `relPath` is the file path relative to the scan
|
||||
* root (posix), so reports read the same for the real workspace and for temp
|
||||
* copies.
|
||||
*/
|
||||
function findDriverImports(files) {
|
||||
const imports = [];
|
||||
for (const file of files) {
|
||||
const stripped = stripComments(file.content);
|
||||
for (const { specifier, index } of extractSpecifiers(stripped)) {
|
||||
if (isDriverSpecifier(specifier)) {
|
||||
imports.push({
|
||||
relPath: file.relPath,
|
||||
line: lineOf(stripped, index),
|
||||
specifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return imports;
|
||||
}
|
||||
|
||||
/** True when a scan-relative posix path sits inside the owner package. */
|
||||
function isInsideOwner(relPath) {
|
||||
return relPath === OWNER_DIR || relPath.startsWith(`${OWNER_DIR}/`);
|
||||
}
|
||||
|
||||
/** Walks the workspace groups under a root and returns [{ relPath, content }] for source files. */
|
||||
async function collectSourceFiles(rootDir) {
|
||||
const files = [];
|
||||
async function walk(dir, relDir) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir(dir, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const abs = path.join(dir, entry.name);
|
||||
const rel = `${relDir}/${entry.name}`;
|
||||
if (entry.isDirectory()) {
|
||||
if (!IGNORED_DIRS.has(entry.name)) await walk(abs, rel);
|
||||
continue;
|
||||
}
|
||||
if (SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
|
||||
files.push({ relPath: rel, content: readFileSync(abs, 'utf8') });
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const group of GROUP_DIRS) {
|
||||
await walk(path.join(rootDir, group), group);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Manifest helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Returns [{ relPath, name, driverDeps }] for every workspace package manifest under a root. */
|
||||
async function collectManifests(rootDir) {
|
||||
const manifests = [];
|
||||
for (const group of GROUP_DIRS) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir(path.join(rootDir, group), { withFileTypes: true });
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const manifestPath = path.join(rootDir, group, entry.name, 'package.json');
|
||||
let manifest;
|
||||
try {
|
||||
manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const allDeps = {
|
||||
...(manifest.dependencies ?? {}),
|
||||
...(manifest.devDependencies ?? {}),
|
||||
};
|
||||
manifests.push({
|
||||
relPath: `${group}/${entry.name}/package.json`,
|
||||
name: manifest.name,
|
||||
driverDeps: Object.keys(allDeps).filter(isDriverSpecifier),
|
||||
});
|
||||
}
|
||||
}
|
||||
return manifests;
|
||||
}
|
||||
|
||||
/** A single driver import, formatted for violation reports. */
|
||||
function formatImport(driverImport) {
|
||||
return ` ${driverImport.relPath}:${driverImport.line} imports "${driverImport.specifier}"`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Temp-copy helpers (mutation probes over the committed tree)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Copies the committed tree (minus ignored dirs) into a fresh temp dir. */
|
||||
function copyCommittedTree() {
|
||||
const dir = mkdtempSync(path.join(os.tmpdir(), 'eppp-db-imports-'));
|
||||
for (const entry of readdirSync(REPO_ROOT, { withFileTypes: true })) {
|
||||
if (IGNORED_DIRS.has(entry.name) || entry.name === 'tests') continue;
|
||||
cpSync(path.join(REPO_ROOT, entry.name), path.join(dir, entry.name), {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
// The isolation test file itself is part of the scan surface in a real
|
||||
// checkout; copying it keeps the temp copy's package graph identical.
|
||||
mkdirSync(path.join(dir, 'tests'), { recursive: true });
|
||||
cpSync(path.join(REPO_ROOT, 'tests'), path.join(dir, 'tests'), { recursive: true });
|
||||
return dir;
|
||||
}
|
||||
|
||||
/** Injects a driver import into a package source inside a tree copy. */
|
||||
function injectDriverImport(rootDir, relPath, line) {
|
||||
const abs = path.join(rootDir, relPath);
|
||||
const original = readFileSync(abs, 'utf8');
|
||||
const injected = `${line}\n${original}`;
|
||||
writeFileSync(abs, injected);
|
||||
return abs;
|
||||
}
|
||||
|
||||
/** Runs the isolation scan over a root and returns the misplaced imports. */
|
||||
async function scanRoot(rootDir) {
|
||||
const files = await collectSourceFiles(rootDir);
|
||||
return findDriverImports(files).filter((d) => !isInsideOwner(d.relPath));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('packages/database-postgres exists, is the driver owner, and its source imports the driver', () => {
|
||||
const manifest = JSON.parse(read(path.join(OWNER_DIR, 'package.json')));
|
||||
assert.equal(manifest.name, '@personal-blog/database-postgres');
|
||||
// The driver is owned here: exact pins, never ranges (reproducibility,
|
||||
// same policy as typescript@6.0.3 at the root).
|
||||
assert.equal(manifest.dependencies?.pg, '8.23.0', 'owner must pin pg exactly');
|
||||
assert.equal(manifest.dependencies?.kysely, '0.29.5', 'owner must pin kysely exactly');
|
||||
assert.equal(manifest.devDependencies?.['@types/pg'], '8.23.1', 'owner must pin @types/pg exactly');
|
||||
|
||||
// The boundary module really imports the driver — the isolation is
|
||||
// exercised, not just declared.
|
||||
const source = read(path.join(OWNER_DIR, 'src', 'index.ts'));
|
||||
assert.match(source, /from\s+'pg'/, 'packages/database-postgres/src/index.ts must import pg');
|
||||
assert.match(source, /from\s+'kysely'/, 'packages/database-postgres/src/index.ts must import kysely');
|
||||
});
|
||||
|
||||
test('pg/Kysely imports are isolated to database-postgres in the real workspace', async () => {
|
||||
const files = await collectSourceFiles(REPO_ROOT);
|
||||
const imports = findDriverImports(files);
|
||||
|
||||
// Non-vacuous: the driver is actually imported somewhere — and that
|
||||
// somewhere is the owner package.
|
||||
assert.ok(imports.length > 0, 'the workspace must contain pg/Kysely imports (in the owner package)');
|
||||
const ownerImports = imports.filter((d) => isInsideOwner(d.relPath));
|
||||
assert.ok(
|
||||
ownerImports.some((d) => d.specifier === 'pg'),
|
||||
'the owner package must import pg',
|
||||
);
|
||||
assert.ok(
|
||||
ownerImports.some((d) => d.specifier === 'kysely'),
|
||||
'the owner package must import kysely',
|
||||
);
|
||||
|
||||
const misplaced = imports.filter((d) => !isInsideOwner(d.relPath));
|
||||
assert.deepEqual(
|
||||
misplaced,
|
||||
[],
|
||||
`no package other than database-postgres may import pg/Kysely:\n${misplaced.map(formatImport).join('\n')}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('no other package declares the database driver in its manifest', async () => {
|
||||
const manifests = await collectManifests(REPO_ROOT);
|
||||
assert.ok(
|
||||
manifests.some((m) => m.relPath.startsWith(`${OWNER_DIR}/`)),
|
||||
'the owner package manifest must be discovered',
|
||||
);
|
||||
const offenders = manifests.filter((m) => !m.relPath.startsWith(`${OWNER_DIR}/`) && m.driverDeps.length > 0);
|
||||
assert.deepEqual(
|
||||
offenders,
|
||||
[],
|
||||
`only ${OWNER_DIR} may declare pg/kysely as a dependency:\n` +
|
||||
offenders
|
||||
.map((m) => ` ${m.relPath} (${m.name}) declares ${m.driverDeps.join(', ')}`)
|
||||
.join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
test('the isolation scan catches a driver import injected into another package (mutation probe)', async () => {
|
||||
const dir = copyCommittedTree();
|
||||
try {
|
||||
injectDriverImport(dir, 'apps/server/src/index.ts', "import { Pool } from 'pg';");
|
||||
const misplaced = await scanRoot(dir);
|
||||
assert.equal(misplaced.length, 1, `expected exactly one violation, got:\n${misplaced.map(formatImport).join('\n')}`);
|
||||
assert.equal(misplaced[0].relPath, 'apps/server/src/index.ts');
|
||||
assert.equal(misplaced[0].specifier, 'pg');
|
||||
assert.equal(misplaced[0].line, 1);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('a copy of the committed tree without misplaced driver imports passes the scan (probe sanity)', async () => {
|
||||
const dir = copyCommittedTree();
|
||||
try {
|
||||
const misplaced = await scanRoot(dir);
|
||||
assert.deepEqual(
|
||||
misplaced,
|
||||
[],
|
||||
`a clean copy of the committed tree must pass the isolation scan:\n${misplaced.map(formatImport).join('\n')}`,
|
||||
);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('the specifier matcher flags pg/kysely imports and ignores other packages (unit probe)', () => {
|
||||
for (const spec of ['pg', 'pg/connection-string', 'kysely', 'kysely/plugin/dialect']) {
|
||||
assert.ok(isDriverSpecifier(spec), `"${spec}" must be flagged as a driver import`);
|
||||
}
|
||||
for (const spec of ['fastify', 'node:http', '@types/pg', 'pg-native', 'pg-pool', 'postgres-array', './internal']) {
|
||||
assert.equal(isDriverSpecifier(spec), false, `"${spec}" must not be flagged as a driver import`);
|
||||
}
|
||||
});
|
||||
|
||||
test('comment stripping ignores commented-out driver imports (unit probe)', () => {
|
||||
const src = [
|
||||
'// import { Pool } from \'pg\';',
|
||||
'/* import { Kysely } from "kysely"; */',
|
||||
"import { Pool } from 'pg';",
|
||||
'',
|
||||
].join('\n');
|
||||
const imports = findDriverImports([{ relPath: 'apps/server/src/index.ts', content: src }]);
|
||||
assert.equal(imports.length, 1);
|
||||
assert.equal(imports[0].specifier, 'pg');
|
||||
assert.equal(imports[0].line, 3, 'the stripped line number must match the original source');
|
||||
});
|
||||
|
||||
test('the isolation criterion is enforced in CI', () => {
|
||||
// Picked up by the root test command (root `scripts.test` glob).
|
||||
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 this suite runs with the rest`,
|
||||
);
|
||||
// And a dedicated CI job gates it on every PR.
|
||||
const workflow = read('.gitea/workflows/ci.yml');
|
||||
assert.ok(
|
||||
workflow.includes(`node --test tests/database-postgres-imports.test.mjs`),
|
||||
`CI must run the isolation suite (job "${CI_JOB}") on every PR`,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user