#!/usr/bin/env node import assert from 'node:assert/strict' import { spawn } from 'node:child_process' import { once } from 'node:events' import { createServer } from 'node:http' import path from 'node:path' import { fileURLToPath } from 'node:url' import { WebSocket } from 'ws' import { createOntologyMcpServer } from '../../../ontology-core/src/mcp-server.mjs' const INTERNAL_TOKEN = 'hub-ontology-mcp-smoke-token' const PAIRING_CODE = 'ONTOLOGYSMOKE1' const currentFile = fileURLToPath(import.meta.url) const hubRoot = path.resolve(path.dirname(currentFile), '..', '..') const ontologyPort = await availablePort() const hubPort = await availablePort() const ontologyServer = createOntologyMcpServer({ internalTokens: [INTERNAL_TOKEN], allowedOrigins: [], maxBodyBytes: 1024 * 1024, }) const hubProcess = spawn(process.execPath, ['src/server.mjs'], { cwd: hubRoot, env: { ...process.env, PORT: String(hubPort), AI_WORKSPACE_HUB_TOKEN: INTERNAL_TOKEN, NODEDC_INTERNAL_ACCESS_TOKEN: INTERNAL_TOKEN, NODEDC_ONTOLOGY_CORE_URL: `http://127.0.0.1:${ontologyPort}`, NODEDC_AI_WORKSPACE_ASSISTANT_URL: 'http://127.0.0.1:1', }, stdio: ['ignore', 'pipe', 'pipe'], }) let hubOutput = '' hubProcess.stdout.on('data', (chunk) => { hubOutput += String(chunk) }) hubProcess.stderr.on('data', (chunk) => { hubOutput += String(chunk) }) let worker = null try { await new Promise((resolve) => ontologyServer.listen(ontologyPort, '127.0.0.1', resolve)) await waitForHub(`http://127.0.0.1:${hubPort}/healthz`, hubProcess) worker = new WebSocket( `ws://127.0.0.1:${hubPort}/api/ai-workspace/hub?pairingCode=${PAIRING_CODE}&machineName=ontology-smoke`, ) await once(worker, 'open') worker.send(JSON.stringify({ type: 'hello', agentVersion: 'smoke', protocolVersion: 'ai-workspace-bridge/v1', capabilities: ['smoke'], })) const response = await fetch( `http://127.0.0.1:${hubPort}/api/ai-workspace/hub/v1/ontology-mcp/${PAIRING_CODE}/mcp`, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream', 'mcp-protocol-version': '2025-06-18', }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'ontology_get_entity', arguments: { term: 'helius' }, }, }), }, ) const payload = await response.json() assert.equal(response.ok, true) assert.equal(response.headers.get('mcp-protocol-version'), '2025-06-18') assert.equal(payload.result.structuredContent.entity.id, 'gelios.integration') worker.close() await once(worker, 'close') const offline = await fetch( `http://127.0.0.1:${hubPort}/api/ai-workspace/hub/v1/ontology-mcp/${PAIRING_CODE}/mcp`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' }, ) assert.equal(offline.status, 503) console.log(JSON.stringify({ ok: true, checks: [ 'pairing_bound_hub_proxy', 'internal_bearer_replaced_by_hub', 'ontology_mcp_tool_response', 'mcp_protocol_header_forwarded', 'offline_pairing_is_rejected', ], }, null, 2)) } finally { if (worker && worker.readyState === WebSocket.OPEN) worker.close() if (hubProcess.exitCode === null && !hubProcess.killed) { hubProcess.kill('SIGTERM') await once(hubProcess, 'exit').catch(() => {}) } await new Promise((resolve, reject) => ontologyServer.close((error) => error ? reject(error) : resolve())) if (hubProcess.exitCode && hubProcess.exitCode !== 0) { throw new Error(`hub_smoke_child_failed:${hubOutput.slice(-2000)}`) } } async function availablePort() { const server = createServer() await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) const port = server.address().port await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())) return port } async function waitForHub(url, processRef) { for (let attempt = 0; attempt < 40; attempt += 1) { if (processRef.exitCode !== null) throw new Error('hub_smoke_child_exited_early') try { const response = await fetch(url) if (response.ok) return } catch {} await new Promise((resolve) => setTimeout(resolve, 100)) } throw new Error('hub_smoke_health_timeout') }