feat: 完善连接管理、SQLite支持及项目文档
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// Real executable/listener smoke test with isolated databases and manual keys.
|
||||
// No production data, desktop DPAPI material or upstream servers are used.
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { basename, join, resolve, sep } from 'node:path';
|
||||
import { setTimeout as delay } from 'node:timers/promises';
|
||||
|
||||
assert(process.argv[2], 'Provide the freshly built serviceport executable');
|
||||
const binary = resolve(process.argv[2]);
|
||||
const temporaryRoot = resolve(tmpdir());
|
||||
const directory = mkdtempSync(join(temporaryRoot, 'serviceport-random-port-'));
|
||||
const inherited = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.toUpperCase().startsWith('SERVICEPORT_')));
|
||||
const masterKey = randomBytes(32).toString('base64');
|
||||
const children = [];
|
||||
|
||||
async function stop(child) {
|
||||
if (child.exitCode !== null || child.signalCode !== null) return;
|
||||
await new Promise(resolve => { child.once('exit', resolve); child.kill(); });
|
||||
}
|
||||
|
||||
async function start(name, address) {
|
||||
const env = {
|
||||
...inherited, SERVICEPORT_MASTER_KEY: masterKey, SERVICEPORT_AUTH_ENABLED: 'false',
|
||||
SERVICEPORT_DATA: join(directory, name, 'test.sqlite'), SERVICEPORT_NO_BROWSER: '1',
|
||||
};
|
||||
if (address !== undefined) env.SERVICEPORT_HTTP_ADDR = address;
|
||||
const child = spawn(binary, [], {cwd: directory, env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe']});
|
||||
children.push(child);
|
||||
let output = '', failure;
|
||||
child.stdout.on('data', data => { output += data.toString(); });
|
||||
child.stderr.on('data', data => { output += data.toString(); });
|
||||
child.on('error', error => { failure = error; });
|
||||
for (let i = 0; i < 200; i++) {
|
||||
if (failure) throw failure;
|
||||
assert(child.exitCode === null, `Startup failed: ${output}`);
|
||||
const match = /http:\/\/127\.0\.0\.1:(\d+)/.exec(output);
|
||||
if (match) {
|
||||
const url = new URL(match[0]);
|
||||
assert(Number(url.port) > 0, 'Startup must report the bound port, not port 0');
|
||||
const health = await fetch(`${url.origin}/healthz`, {signal: AbortSignal.timeout(5000)});
|
||||
assert.equal(health.status, 200);
|
||||
const page = await fetch(url, {signal: AbortSignal.timeout(5000)});
|
||||
assert.equal(page.status, 200);
|
||||
assert.match(await page.text(), /<html/i);
|
||||
return {child, url};
|
||||
}
|
||||
await delay(50);
|
||||
}
|
||||
throw new Error(`Startup did not report its actual URL: ${output}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const first = await start('first');
|
||||
const second = await start('second');
|
||||
assert.notEqual(first.url.port, second.url.port, 'Simultaneous default launches must not collide');
|
||||
await stop(first.child);
|
||||
const fixed = await start('first', first.url.host);
|
||||
assert.equal(fixed.url.origin, first.url.origin, 'Explicit fixed address must be respected');
|
||||
console.log(`Random-port startup verified: ${first.url.origin}, ${second.url.origin}; fixed-port override and UI/health endpoints passed.`);
|
||||
} finally {
|
||||
await Promise.all(children.map(stop));
|
||||
const target = resolve(directory);
|
||||
assert(target.startsWith(temporaryRoot + sep) && basename(target).startsWith('serviceport-random-port-'), 'Unexpected cleanup path');
|
||||
rmSync(target, {recursive: true, force: true});
|
||||
}
|
||||
Reference in New Issue
Block a user