import test from 'node:test'; import assert from 'node:assert/strict'; import { createServer } from 'node:http'; import { spawn } from 'node:child_process'; import { once } from 'node:events'; test('本地预览资料与行业均读受控上游,拒绝写命令、不透传凭据、错误不回退旧库', async () => { const received = []; const upstream = createServer(async (req, res) => { const chunks = [];for await (const c of req) chunks.push(c); const body = JSON.parse(Buffer.concat(chunks));received.push({ path: req.url, headers: req.headers, body }); res.writeHead(body.generation === 'stale' ? 409 : 200, { 'content-type': 'application/json' }); res.end(JSON.stringify(body.generation === 'stale' ? { ok: false, error: { code: 'LIBRARY_VERSION_CHANGED' } } : { ok: true, generation: 'unified-test-generation', readOnly: true, databaseWrites: 0 })); }); await new Promise(resolve => upstream.listen(0, '127.0.0.1', resolve)); const upstreamUrl = `http://127.0.0.1:${upstream.address().port}`; const reserve = createServer();await new Promise(resolve => reserve.listen(0, '127.0.0.1', resolve)); const port = reserve.address().port;await new Promise(resolve => reserve.close(resolve)); const env = { ...process.env }; for (const k of ['KSF_UNIFIED_DB_CONFIG', 'KSF_LIBRARY_REGISTRY', 'KSF_VOC_DB', 'KSF_INDUSTRY_SNAPSHOT']) delete env[k]; env.KSF_LIBRARY_REMOTE_URL = upstreamUrl + '/api/library';env.KSF_INDUSTRY_REMOTE_URL = upstreamUrl + '/api/industry'; const child = spawn(process.execPath, ['src/dev/serve.mjs', '--port', String(port)], { env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }); try { await new Promise((resolve, reject) => { const timer = setTimeout(() => reject(Error('preview startup timeout')), 15000); child.on('exit', code => { clearTimeout(timer);reject(Error('preview exited ' + code)); }); child.on('error', e => { clearTimeout(timer);reject(e); }); child.stdout.on('data', part => { if (String(part).includes('KF dev server')) { clearTimeout(timer);resolve(); } }); }); const base = `http://127.0.0.1:${port}`; const send = (path, body) => fetch(base + path, { method: 'POST', headers: { 'content-type': 'application/json', authorization: 'Bearer must-stay-local', 'x-parse-master-key': 'must-stay-local', }, body: JSON.stringify(body) }); const info = await (await fetch(base + '/__dev/info')).json(); assert.equal(info.librarySource, 'production-readonly');assert.equal(info.industrySource, 'production-readonly'); for (const [route, op] of [['/api/library', 'summary'], ['/api/industry', 'contract'], ['/api/industry', 'plan']]) { const response = await send(route, { op, filters: { platform: 'jd' } }); const data = await response.json();assert.equal(response.status, 200);assert.equal(data.generation, 'unified-test-generation');assert.equal(data.localPreview, true); assert.equal(received.at(-1).path, route);assert.equal(received.at(-1).headers.authorization, undefined); assert.equal(received.at(-1).headers['x-parse-master-key'], undefined);assert.equal(received.at(-1).body.filters.platform, 'jd'); } const count = received.length; for (const route of ['/api/library', '/api/industry']) { assert.equal((await send(route, { op: 'apply' })).status, 400); assert.equal((await send(route, [])).status, 400); assert.equal((await fetch(base + route)).status, 405); assert.equal((await fetch(base + route, { method: 'POST', body: 'x'.repeat(24001) })).status, 413); } assert.equal(received.length, count); const failed = await send('/api/industry', { op: 'contract', generation: 'stale' }); assert.equal(failed.status, 409);assert.equal((await failed.json()).error.code, 'LIBRARY_VERSION_CHANGED'); } finally { if (child.exitCode === null) { const exited = once(child, 'exit');child.kill();await exited; } await new Promise(resolve => upstream.close(resolve)); } });