68 lines
4.2 KiB
JavaScript
68 lines
4.2 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { spawn, execFileSync } from 'node:child_process'
|
|
import { X509Certificate, createHash, createPrivateKey, randomBytes } from 'node:crypto'
|
|
import { readFileSync, existsSync, mkdtempSync, rmSync } from 'node:fs'
|
|
import { createServer } from 'node:net'
|
|
import { tmpdir } from 'node:os'
|
|
import { resolve, join, relative, isAbsolute } from 'node:path'
|
|
import { setTimeout as delay } from 'node:timers/promises'
|
|
|
|
const directory = resolve(process.argv[2] || '')
|
|
assert(process.argv[2], 'Provide an extracted package directory')
|
|
const binary = join(directory, 'agentdb.exe')
|
|
const certTool = join(directory, 'devcert.exe')
|
|
for (const name of ['agentdb.exe', 'devcert.exe', 'README.md', '.env.example', 'build-info.json', 'SHA256SUMS.txt']) assert(existsSync(join(directory, name)), `Missing ${name}`)
|
|
for (const line of readFileSync(join(directory, 'SHA256SUMS.txt'), 'utf8').trim().split(/\r?\n/)) {
|
|
const match = /^([a-f0-9]{64}) (.+)$/.exec(line)
|
|
assert(match, 'Invalid checksum entry')
|
|
const filename = resolve(directory, match[2])
|
|
const within = relative(directory, filename)
|
|
assert(within && !within.startsWith('..') && !isAbsolute(within), 'Checksum path outside package')
|
|
assert.equal(createHash('sha256').update(readFileSync(filename)).digest('hex'), match[1], `Checksum mismatch: ${match[2]}`)
|
|
}
|
|
const key = execFileSync(binary, ['generate-key'], { encoding: 'utf8', windowsHide: true }).trim()
|
|
assert.equal(Buffer.from(key, 'base64').length, 32)
|
|
const temp = mkdtempSync(join(tmpdir(), 'agentdb-package-smoke-'))
|
|
let child
|
|
try {
|
|
const tlsDir = join(temp, 'tls')
|
|
execFileSync(certTool, [tlsDir], { windowsHide: true })
|
|
const cert = new X509Certificate(readFileSync(join(tlsDir, 'cert.pem')))
|
|
assert(cert.checkHost('localhost'))
|
|
assert(cert.checkPrivateKey(createPrivateKey(readFileSync(join(tlsDir, 'key.pem')))))
|
|
const listener = createServer()
|
|
await new Promise((resolve, reject) => { listener.once('error', reject); listener.listen(0, '127.0.0.1', resolve) })
|
|
const port = listener.address().port
|
|
await new Promise(resolve => listener.close(resolve))
|
|
const password = randomBytes(24).toString('base64url')
|
|
child = spawn(binary, [], { cwd: directory, windowsHide: true, stdio: ['ignore', 'ignore', 'pipe'], env: { ...process.env, AGENTDB_AUTH_ENABLED: 'true', AGENTDB_MASTER_KEY: key, AGENTDB_ADMIN_USER: 'package-test', AGENTDB_ADMIN_PASSWORD: password, AGENTDB_DATA: join(temp, 'test.sqlite'), AGENTDB_HTTP_ADDR: `127.0.0.1:${port}`, AGENTDB_SECURE_COOKIE: 'false', AGENTDB_TLS_CERT: join(tlsDir, 'cert.pem'), AGENTDB_TLS_KEY: join(tlsDir, 'key.pem') } })
|
|
let startupError = ''
|
|
child.stderr.on('data', b => { startupError += b.toString() })
|
|
child.on('error', e => { startupError += e.message })
|
|
const base = `http://127.0.0.1:${port}`
|
|
let healthy = false
|
|
for (let i = 0; i < 60; i++) {
|
|
try { healthy = (await fetch(`${base}/healthz`, { signal: AbortSignal.timeout(500) })).ok } catch {}
|
|
if (healthy) break
|
|
if (child.exitCode !== null) throw new Error(`Package exited during startup: ${startupError}`)
|
|
await delay(100)
|
|
}
|
|
assert(healthy, 'Packaged application did not start')
|
|
const page = await fetch(base)
|
|
assert.equal(page.status, 200)
|
|
const html = await page.text()
|
|
const assets = [...html.matchAll(/(?:src|href)="(\/assets\/[^" ]+)"/g)].map(m => m[1])
|
|
assert(assets.length >= 2, 'Built frontend was not embedded')
|
|
for (const asset of assets) assert.equal((await fetch(base + asset)).status, 200, `Missing embedded ${asset}`)
|
|
const auth = await fetch(`${base}/api/v1/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'package-test', password }) })
|
|
assert.equal(auth.status, 200)
|
|
const cookie = auth.headers.get('set-cookie').split(';')[0]
|
|
const proxies = await fetch(`${base}/api/v1/proxies`, { headers: { Cookie: cookie } })
|
|
assert.equal(proxies.status, 200)
|
|
assert.deepEqual(await proxies.json(), [])
|
|
console.log('Package verified: file checksums, key generation, certificate utility, isolated startup, embedded web assets and administrator login.')
|
|
} finally {
|
|
if (child && child.exitCode === null) { await new Promise(resolve => { child.once('exit', resolve); child.kill() }) }
|
|
rmSync(temp, { recursive: true, force: true })
|
|
}
|