88 lines
6.1 KiB
JavaScript
88 lines
6.1 KiB
JavaScript
// Isolated regression only: no application config, configured DB URL or startup.
|
|
import { mkdtemp, rm, readFile, writeFile, mkdir } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join, resolve, relative, isAbsolute, basename } from 'node:path'
|
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
import { createRequire } from 'node:module'
|
|
import { randomBytes, createHash } from 'node:crypto'
|
|
import { createServer } from 'node:net'
|
|
import { spawn } from 'node:child_process'
|
|
|
|
const args = process.argv.slice(2)
|
|
if (args.length > 1 || args[0] && !args[0].startsWith('--source-root=')) throw Error('Only --source-root=PATH review override is supported')
|
|
const sourceRoot = args[0] ? resolve(args[0].slice('--source-root='.length)) : fileURLToPath(new URL('../../', import.meta.url))
|
|
const require = createRequire(join(sourceRoot, 'backend/package.json'))
|
|
const { default: EmbeddedPostgres } = await import(pathToFileURL(require.resolve('embedded-postgres')).href)
|
|
const sources = ['backend/src/services/partition-write-transaction.ts', 'backend/src/db/sqlite-import-control-writes.ts',
|
|
'backend/src/db/migration-schema-inventory.ts', 'backend/src/db/migration-data-inventory.ts', 'backend/src/db/schema.ts', 'backend/src/db/index.ts']
|
|
const ownFiles = [fileURLToPath(import.meta.url), fileURLToPath(new URL('../src/db/database-source-capture-adopted.sql-helper.ts', import.meta.url)),
|
|
fileURLToPath(new URL('../src/db/database-source-capture-adopted.sql.test.ts', import.meta.url))]
|
|
async function pins() {
|
|
const files = [...sources.map(path => ({ path, absolute: join(sourceRoot, path) })), ...ownFiles.map(absolute => ({ path: 'executed-fixture/' + basename(absolute), absolute }))]
|
|
return Promise.all(files.map(async ({ path, absolute }) => ({ path, sha256: createHash('sha256').update(await readFile(absolute)).digest('hex') })))
|
|
}
|
|
const before = await pins()
|
|
const directory = await mkdtemp(join(tmpdir(), 'mm-source-capture-'))
|
|
const password = randomBytes(24).toString('hex'), ownerToken = randomBytes(24).toString('hex')
|
|
let postgres, failure, phase = 'port-and-owner-manifest', failedPhase, childExitCode = 1, unchanged = false, clusterStopped = false, directoryRemoved = false
|
|
try {
|
|
const listener = createServer()
|
|
await new Promise((done, reject) => { listener.once('error', reject); listener.listen(0, '127.0.0.1', done) })
|
|
const port = listener.address().port
|
|
await new Promise((done, reject) => listener.close(error => error ? reject(error) : done()))
|
|
await writeFile(join(directory, 'owned-source-capture.json'), JSON.stringify({ ownerToken, port, username: 'source_operator' }), { flag: 'wx' })
|
|
postgres = new EmbeddedPostgres({ databaseDir: join(directory, 'pg'), port, user: 'source_operator', password,
|
|
persistent: true, createPostgresUser: false, initdbFlags: ['--encoding=UTF8'], postgresFlags: ['-h', '127.0.0.1'], onLog: () => {}, onError: () => {} })
|
|
phase = 'initialise-owned-postgres'; await postgres.initialise()
|
|
phase = 'start-owned-postgres'; await postgres.start()
|
|
phase = 'owned-sql-child'
|
|
childExitCode = await new Promise((done, reject) => {
|
|
const child = spawn(process.execPath, ['--import', pathToFileURL(require.resolve('tsx')).href, ownFiles[2]], {
|
|
cwd: directory, windowsHide: true, stdio: 'inherit', env: {
|
|
...Object.fromEntries(['SystemRoot', 'WINDIR', 'TEMP', 'TMP', 'PATH', 'Path'].filter(key => process.env[key]).map(key => [key, process.env[key]])),
|
|
MMAPP_OWN_SOURCE_CAPTURE: 'true', MMAPP_OWN_SOURCE_CAPTURE_DIRECTORY: directory, MMAPP_OWN_SOURCE_CAPTURE_ROOT: sourceRoot,
|
|
MMAPP_OWN_SOURCE_CAPTURE_TOKEN: ownerToken, MMAPP_OWN_SOURCE_CAPTURE_ADMIN_URL: `postgresql://source_operator:${password}@127.0.0.1:${port}/postgres`,
|
|
},
|
|
})
|
|
// No child subprocesses are spawned by the SQL fixture. Kill only this
|
|
// exact Node child on timeout, then await exit before stopping own cluster.
|
|
let timedOut = false
|
|
const timer = setTimeout(() => { timedOut = true; child.kill(); }, 120000)
|
|
child.once('error', error => { clearTimeout(timer); reject(error) })
|
|
child.once('exit', code => { clearTimeout(timer); done(timedOut ? 1 : code ?? 1) })
|
|
})
|
|
} catch (error) { failure = error; failedPhase = phase }
|
|
finally {
|
|
try {
|
|
if (postgres) await postgres.stop()
|
|
clusterStopped = true
|
|
} catch (error) { failure ??= error }
|
|
// Delete only when stop succeeded, retaining failed-stop files for diagnosis.
|
|
if (clusterStopped) {
|
|
try {
|
|
const root = resolve(tmpdir()), target = resolve(directory), rel = relative(root, target)
|
|
if (!rel || rel.startsWith('..') || isAbsolute(rel) || !basename(target).startsWith('mm-source-capture-')) throw Error('Unsafe owned source capture cleanup path')
|
|
await rm(target, { recursive: true, force: true }); directoryRemoved = true
|
|
} catch (error) { failure ??= error }
|
|
}
|
|
try {
|
|
const after = await pins()
|
|
unchanged = JSON.stringify(before) === JSON.stringify(after)
|
|
await mkdir(join(sourceRoot, 'tmp'), { recursive: true })
|
|
const evidencePath = join(sourceRoot, 'tmp', basename(directory) + '-pins.json')
|
|
await writeFile(evidencePath, JSON.stringify({ format: 'adopted-source-capture-owned-pins-v1', date: new Date().toISOString(),
|
|
before, after, unchanged, childExitCode, clusterStopped, directoryRemoved,
|
|
allWriterDrainAuthenticated: false, sourceGenerationAuthenticated: false, readyAuthorized: false }, null, 2))
|
|
console.log('Owned source capture evidence:', evidencePath)
|
|
} catch (error) { failure ??= error }
|
|
}
|
|
console.log(JSON.stringify({ childExitCode, sourcePinsUnchanged: unchanged, clusterStopped, directoryRemoved,
|
|
allWriterDrainAuthenticated: false, sourceGenerationAuthenticated: false, readyAuthorized: false }))
|
|
// Never print connection strings or credentials in fixture error output.
|
|
if (failure) {
|
|
const text = String(failure)
|
|
const knownCode = ['ENOMEM', 'EACCES', 'EPERM', 'ENOENT', 'EADDRINUSE', 'ENOSPC'].find(code => text.includes(code)) ?? 'UNCLASSIFIED'
|
|
console.error(JSON.stringify({ ownedRunnerFailure: true, phase: failedPhase ?? 'pins-or-cleanup', knownCode }))
|
|
}
|
|
process.exitCode = failure || !unchanged || !clusterStopped || !directoryRemoved ? 1 : childExitCode
|