/** * integrator-tools 单测 */ const fs = require('fs'); const path = require('path'); const os = require('os'); const http = require('http'); const TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'devloop-it-')); process.env.HOME = TEST_HOME; const pathResolver = require('../../../services/devloop/path-resolver'); const tools = require('../../../src/tools/devloop/integrator-tools'); function setupProject() { pathResolver._resetForTesting(); const root = fs.mkdtempSync(path.join(os.tmpdir(), 'it-proj-')); const pid = 'p-integtest'; const reg = { [pid]: { root_path: root, mode: 'linked-new', company_id: 'c-test' }, }; fs.mkdirSync(path.join(TEST_HOME, '.devloop'), { recursive: true }); fs.writeFileSync( path.join(TEST_HOME, '.devloop', 'linked-projects.json'), JSON.stringify(reg) ); pathResolver._resetForTesting(); return { pid, root }; } describe('integrator-tools', () => { let env, ctx; beforeEach(() => { env = setupProject(); ctx = { pid: env.pid, agent: 'integrator' }; }); afterEach(() => { fs.rmSync(env.root, { recursive: true, force: true }); }); describe('port_picker', () => { it('allocates the requested number of free ports', async () => { const r = await tools.port_picker.execute({ count: 3 }, ctx); expect(r.ok).toBe(true); expect(r.ports).toHaveLength(3); for (const p of r.ports) { expect(p).toBeGreaterThanOrEqual(18000); expect(p).toBeLessThan(19000); } expect(new Set(r.ports).size).toBe(3); // unique }); }); describe('parse_placeholder', () => { it('resolves {{step0.body.field}} from prev results', async () => { const r = await tools.parse_placeholder.execute({ template: { token: '{{step0.token}}' }, previous_steps: [{ body: { token: 'abc123' } }], }, ctx); expect(r.resolved.token).toBe('abc123'); }); it('throws when step ref missing', async () => { await expect(tools.parse_placeholder.execute({ template: { x: '{{step5.foo}}' }, previous_steps: [], }, ctx)).rejects.toThrow(/placeholder step5/); }); }); describe('health_wait', () => { it('returns ok=true when server responds 200', async () => { const srv = http.createServer((_req, res) => { res.statusCode = 200; res.end('ok'); }); await new Promise(r => srv.listen(0, '127.0.0.1', r)); const port = srv.address().port; const r = await tools.health_wait.execute({ url: `http://127.0.0.1:${port}/api/health`, timeout_ms: 2000, interval_ms: 50, }, ctx); srv.close(); expect(r.ok).toBe(true); expect(r.status).toBe(200); }); it('returns ok=false on timeout', async () => { const r = await tools.health_wait.execute({ url: 'http://127.0.0.1:1/never', timeout_ms: 300, interval_ms: 100, }, ctx); expect(r.ok).toBe(false); expect(r.timeout).toBe(true); }); }); describe('collect_stderr', () => { it('returns last N lines from log', async () => { const p = path.join(env.root, 'process.log'); fs.writeFileSync(p, ['l1','l2','l3','l4','l5'].join('\n')); const r = await tools.collect_stderr.execute({ log_path: p, lines: 2 }, ctx); expect(r.ok).toBe(true); expect(r.excerpt).toBe('l4\nl5'); }); it('returns ok=false when log missing', async () => { const r = await tools.collect_stderr.execute({ log_path: path.join(env.root, 'nope.log'), }, ctx); expect(r.ok).toBe(false); }); }); describe('run_case', () => { it('executes a single GET case against a test server', async () => { const srv = http.createServer((req, res) => { if (req.url === '/api/ping') { res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ pong: true })); return; } res.statusCode = 404; res.end(); }); await new Promise(r => srv.listen(0, '127.0.0.1', r)); const port = srv.address().port; const r = await tools.run_case.execute({ case_spec: { steps: [ { unit: 'u-x', call: 'GET /api/ping', expect: { status: 200 } }, ], }, ports: { 'u-x': port }, }, ctx); srv.close(); expect(r.ok).toBe(true); expect(r.steps_ran).toBe(1); }); it('reports failed step when status mismatches', async () => { const srv = http.createServer((_req, res) => { res.statusCode = 500; res.end('{}'); }); await new Promise(r => srv.listen(0, '127.0.0.1', r)); const port = srv.address().port; const r = await tools.run_case.execute({ case_spec: { steps: [{ unit: 'u-x', call: 'GET /api/x', expect: { status: 200 } }], }, ports: { 'u-x': port }, }, ctx); srv.close(); expect(r.ok).toBe(false); expect(r.failed_step).toBe(0); expect(r.got.status).toBe(500); }); }); });