dev-research-proxy.test.mjs 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. import test from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import { createServer } from 'node:http';
  4. import { spawn } from 'node:child_process';
  5. import { once } from 'node:events';
  6. test('本地预览资料与行业均读受控上游,拒绝写命令、不透传凭据、错误不回退旧库', async () => {
  7. const received = [];
  8. const upstream = createServer(async (req, res) => {
  9. const chunks = [];for await (const c of req) chunks.push(c);
  10. const body = JSON.parse(Buffer.concat(chunks));received.push({ path: req.url, headers: req.headers, body });
  11. res.writeHead(body.generation === 'stale' ? 409 : 200, { 'content-type': 'application/json' });
  12. res.end(JSON.stringify(body.generation === 'stale'
  13. ? { ok: false, error: { code: 'LIBRARY_VERSION_CHANGED' } }
  14. : { ok: true, generation: 'unified-test-generation', readOnly: true, databaseWrites: 0 }));
  15. });
  16. await new Promise(resolve => upstream.listen(0, '127.0.0.1', resolve));
  17. const upstreamUrl = `http://127.0.0.1:${upstream.address().port}`;
  18. const reserve = createServer();await new Promise(resolve => reserve.listen(0, '127.0.0.1', resolve));
  19. const port = reserve.address().port;await new Promise(resolve => reserve.close(resolve));
  20. const env = { ...process.env };
  21. for (const k of ['KSF_UNIFIED_DB_CONFIG', 'KSF_LIBRARY_REGISTRY', 'KSF_VOC_DB', 'KSF_INDUSTRY_SNAPSHOT']) delete env[k];
  22. env.KSF_LIBRARY_REMOTE_URL = upstreamUrl + '/api/library';env.KSF_INDUSTRY_REMOTE_URL = upstreamUrl + '/api/industry';
  23. const child = spawn(process.execPath, ['src/dev/serve.mjs', '--port', String(port)], { env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
  24. try {
  25. await new Promise((resolve, reject) => {
  26. const timer = setTimeout(() => reject(Error('preview startup timeout')), 15000);
  27. child.on('exit', code => { clearTimeout(timer);reject(Error('preview exited ' + code)); });
  28. child.on('error', e => { clearTimeout(timer);reject(e); });
  29. child.stdout.on('data', part => { if (String(part).includes('KF dev server')) { clearTimeout(timer);resolve(); } });
  30. });
  31. const base = `http://127.0.0.1:${port}`;
  32. const send = (path, body) => fetch(base + path, { method: 'POST', headers: {
  33. 'content-type': 'application/json', authorization: 'Bearer must-stay-local', 'x-parse-master-key': 'must-stay-local',
  34. }, body: JSON.stringify(body) });
  35. const info = await (await fetch(base + '/__dev/info')).json();
  36. assert.equal(info.librarySource, 'production-readonly');assert.equal(info.industrySource, 'production-readonly');
  37. for (const [route, op] of [['/api/library', 'summary'], ['/api/industry', 'contract'], ['/api/industry', 'plan']]) {
  38. const response = await send(route, { op, filters: { platform: 'jd' } });
  39. const data = await response.json();assert.equal(response.status, 200);assert.equal(data.generation, 'unified-test-generation');assert.equal(data.localPreview, true);
  40. assert.equal(received.at(-1).path, route);assert.equal(received.at(-1).headers.authorization, undefined);
  41. assert.equal(received.at(-1).headers['x-parse-master-key'], undefined);assert.equal(received.at(-1).body.filters.platform, 'jd');
  42. }
  43. const count = received.length;
  44. for (const route of ['/api/library', '/api/industry']) {
  45. assert.equal((await send(route, { op: 'apply' })).status, 400);
  46. assert.equal((await send(route, [])).status, 400);
  47. assert.equal((await fetch(base + route)).status, 405);
  48. assert.equal((await fetch(base + route, { method: 'POST', body: 'x'.repeat(24001) })).status, 413);
  49. }
  50. assert.equal(received.length, count);
  51. const failed = await send('/api/industry', { op: 'contract', generation: 'stale' });
  52. assert.equal(failed.status, 409);assert.equal((await failed.json()).error.code, 'LIBRARY_VERSION_CHANGED');
  53. } finally {
  54. if (child.exitCode === null) { const exited = once(child, 'exit');child.kill();await exited; }
  55. await new Promise(resolve => upstream.close(resolve));
  56. }
  57. });