import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import fs from 'node:fs'; import http from 'node:http'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; const root = path.resolve(import.meta.dirname, '../..'); function listen(server) { return new Promise((resolve) => server.listen(0, '127.0.0.1', () => resolve(server.address().port))); } async function freePort() { const server = http.createServer(); const port = await listen(server); await new Promise((resolve) => server.close(resolve)); return port; } function socketRequest(socketPath, pathname) { return new Promise((resolve, reject) => { const request = http.request({ socketPath, path: pathname }, (response) => { const chunks = []; response.on('data', (chunk) => chunks.push(chunk)); response.on('end', () => { try { resolve({ status: response.statusCode, body: JSON.parse(Buffer.concat(chunks).toString('utf8')) }); } catch (error) { reject(error); } }); }); request.on('error', reject); request.end(); }); } async function httpText(url) { const response = await fetch(url); return { status: response.status, type: response.headers.get('content-type'), body: await response.text() }; } async function waitFor(probe, child, stderr) { for (let attempt = 0; attempt < 100; attempt += 1) { if (child.exitCode !== null) throw new Error(`Compiled Harbor exited early: ${stderr()}`); try { const result = await probe(); if (result) return result; } catch { // The disposable listener is still starting. } await new Promise((resolve) => setTimeout(resolve, 20)); } throw new Error(`Compiled Harbor did not become ready: ${stderr()}`); } async function stop(child) { child.kill('SIGTERM'); if (child.exitCode === null) await new Promise((resolve) => child.once('exit', resolve)); assert.equal(child.exitCode, 0); } test('compiled dispatcher starts and stops control and dataplane contracts', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-compiled-')); const controlData = path.join(directory, 'control'); const dataplaneData = path.join(directory, 'dataplane'); const socketPath = path.join(directory, 'dataplane.sock'); const port = await freePort(); fs.mkdirSync(controlData); fs.mkdirSync(dataplaneData); const children = []; t.after(async () => { for (const child of children) { if (child.exitCode === null) await stop(child); } fs.rmSync(directory, { recursive: true, force: true }); }); const start = (env) => { const child = spawn(process.execPath, ['dist/server/main.js'], { cwd: root, env: { ...process.env, ...env }, stdio: ['ignore', 'ignore', 'pipe'], }); let stderr = ''; child.stderr.on('data', (chunk) => { stderr += chunk; }); children.push(child); return { child, stderr: () => stderr }; }; const control = start({ APP_COMPONENT: 'control', APP_MODE: 'client', DATA_DIR: controlData, DIST_DIR: '', HARBOR_HOST_NETWORK_STATE: path.join(directory, 'missing-network.json'), PORT: String(port), SING_BOX_CACHE: path.join(controlData, 'cache.db'), }); const state = await waitFor(async () => { const response = await fetch(`http://127.0.0.1:${port}/api/state`); return response.ok ? response.json() : null; }, control.child, control.stderr); assert.equal(state.apiVersion, 1); assert.equal(state.mode, 'client'); const page = await httpText(`http://127.0.0.1:${port}/`); assert.equal(page.status, 200); assert.match(page.type, /^text\/html/); assert.match(page.body, /