gongxiangp2/scripts/database-migrator-lock.test.mjs

113 lines
6.2 KiB
JavaScript

import assert from 'node:assert/strict'
import { existsSync, readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import test from 'node:test'
import { createMigratorLock } from './database-migrator-lock.mjs'
const read = relative => JSON.parse(readFileSync(fileURLToPath(new URL(relative, import.meta.url)), 'utf8'))
const root = read('../package-lock.json')
const manifest = read('../workers/database-migrator/package.json')
const lock = read('../workers/database-migrator/package-lock.json')
test('committed migrator lock is the exact reproducible main-lock subset', () => {
assert.deepEqual(lock, createMigratorLock(root, manifest))
assert.ok(Object.keys(lock.packages).length < 50)
for (const [name, record] of Object.entries(lock.packages)) {
if (!name) continue
for (const key of ['version', 'resolved', 'integrity']) assert.equal(record[key], root.packages[name][key])
assert.equal(record.dev, undefined)
assert.equal(record.devOptional, undefined)
}
})
test('Prisma CLI uses its matching nested engines without app Prisma 6 or UI/media/test packages', () => {
assert.equal(lock.packages['node_modules/prisma/node_modules/@prisma/engines'].version, '5.22.0')
for (const name of ['@prisma/engines', 'next', 'react', 'canvas', 'sharp', 'jest', 'typescript', '@aws-sdk/client-s3']) {
assert.equal(lock.packages[`node_modules/${name}`], undefined, `Unexpected app package ${name}`)
}
assert.ok(lock.packages['node_modules/@esbuild/linux-x64'])
assert.ok(lock.packages['node_modules/get-tsconfig'])
})
test('direct dependency version drift and new dependency groups fail closed', () => {
const changedRoot = structuredClone(root)
changedRoot.packages['node_modules/prisma'].version = '6.18.0'
assert.throws(() => createMigratorLock(changedRoot, manifest), /differs from the main lock/)
assert.throws(() => createMigratorLock(root, { ...manifest, devDependencies: { next: '14.2.33' } }), /unreviewed dependency groups/)
assert.throws(() => createMigratorLock(root, { ...manifest, dependencies: { ...manifest.dependencies, next: '14.2.33' } }), /explicitly reviewed/)
})
test('missing transitive package and missing integrity fail closed', () => {
const missing = structuredClone(root)
delete missing.packages['node_modules/get-tsconfig']
assert.throws(() => createMigratorLock(missing, manifest), /Unresolved dependency/)
const unsigned = structuredClone(root)
delete unsigned.packages['node_modules/prisma/node_modules/@prisma/engines'].integrity
assert.throws(() => createMigratorLock(unsigned, manifest), /integrity/)
})
test('optional peer additions require explicit review instead of silently disappearing', () => {
const changed = structuredClone(root)
changed.packages['node_modules/zod'].peerDependencies = { next: '*' }
changed.packages['node_modules/zod'].peerDependenciesMeta = { next: { optional: true } }
assert.throws(() => createMigratorLock(changed, manifest), /Unreviewed optional peer/)
})
test('slim migrator copies the full seed source closure and no unreviewed runtime dependency', () => {
const require = createRequire(import.meta.url)
const ts = require('typescript')
const rootDir = fileURLToPath(new URL('../', import.meta.url))
const files = new Set()
const packages = new Set()
const visitFile = relative => {
relative = relative.replaceAll('\\', '/')
if (files.has(relative)) return
files.add(relative)
const absolute = path.join(rootDir, relative)
const ast = ts.createSourceFile(absolute, readFileSync(absolute, 'utf8'), ts.ScriptTarget.Latest, true)
const resolve = specifier => {
if (specifier.startsWith('node:')) return
if (!specifier.startsWith('.') && !specifier.startsWith('@/')) {
packages.add(specifier)
return
}
const base = specifier.startsWith('@/') ? specifier.slice(2) : path.join(path.dirname(relative), specifier)
const next = [base, `${base}.ts`, `${base}.tsx`, `${base}/index.ts`].find(candidate => existsSync(path.join(rootDir, candidate)))
assert.ok(next, `Unresolved seed import ${specifier} from ${relative}`)
visitFile(next)
}
const visitNode = node => {
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier) resolve(node.moduleSpecifier.text)
if (ts.isCallExpression(node) && node.expression.getText(ast) === 'require' && ts.isStringLiteral(node.arguments[0])) resolve(node.arguments[0].text)
ts.forEachChild(node, visitNode)
}
visitNode(ast)
}
visitFile('scripts/seed-team-function-config.ts')
assert.deepEqual([...packages].sort(), ['@prisma/client', 'zod'])
const dockerfile = readFileSync(path.join(rootDir, 'Dockerfile'), 'utf8')
const migrator = dockerfile.slice(dockerfile.search(/^FROM base AS migrator\r?$/m), dockerfile.search(/^FROM base AS runner\r?$/m))
for (const file of files) assert.ok(migrator.includes(`/app/${file}`), `Seed dependency missing from slim image: ${file}`)
assert.equal(files.size, 14, 'Review seed dependency closure before changing the image copy list')
})
test('Docker locks the isolated install and runs Prisma as nonroot with no network', () => {
const dockerfile = readFileSync(fileURLToPath(new URL('../Dockerfile', import.meta.url)), 'utf8')
assert.match(dockerfile, /node scripts\/database-migrator-lock\.mjs --check/)
const depsStart = dockerfile.search(/^FROM base AS migrator-deps\r?$/m)
assert.ok(depsStart >= 0)
const deps = dockerfile.slice(depsStart, dockerfile.indexOf('\nFROM ', depsStart + 1))
assert.match(deps, /npm ci --no-audit --no-fund/)
assert.match(deps, /PRISMA_SKIP_POSTINSTALL_GENERATE=1 npm ci/)
assert.doesNotMatch(deps, /--ignore-scripts/)
assert.doesNotMatch(deps, /npm (?:install|update)/)
assert.match(deps, /apk add --no-cache openssl/)
const start = dockerfile.search(/^FROM base AS migrator\r?$/m)
const migrator = dockerfile.slice(start, dockerfile.indexOf('\nFROM ', start + 1))
assert.match(migrator, /USER node[\s\S]*RUN --network=none[^\n]*prisma (?:--version|-v)/)
assert.match(migrator, /COPY --from=builder --chown=root:root \/app\/node_modules\/\.prisma/)
assert.match(migrator, /ENTRYPOINT \["node", "scripts\/safe-production-migrate\.mjs"\]/)
})