#!/usr/bin/env node
// E2E Test: C++ server ↔ C++ client via local testnet.
//
// Spawns both test_interop_server_bin and test_interop_client against a
// shared HyperDHT testnet. This tests the pure C++ path — no JS peers.
//
// Flow:
// 1. Create local testnet (3 HyperDHT nodes)
// 2. Spawn C++ server, wait for PUBLIC_KEY + READY
// 3. Spawn C++ client with server's public key
// 4. Both should holepunch/LAN connect and exit 0
//
// Usage: node test_cpp_to_cpp.js
// Requires: test_interop_server_bin + test_interop_client built via build_tests.sh
const createTestnet = require('hyperdht/testnet')
const { spawn } = require('child_process')
const path = require('path')
const TIMEOUT_MS = 20000
async function main () {
console.log('=== E2E Test: C++ Server ↔ C++ Client ===\n')
let passed = 0
let failed = 0
const check = (cond, name) => {
if (cond) {
passed++
console.log(' PASS:', name)
} else {
failed++
console.log(' FAIL:', name)
}
}
// Create local testnet.
console.log('Creating local testnet (3 HyperDHT nodes)...')
const testnet = await createTestnet(3, { teardown: () => {} })
const bootstrapStr = testnet.bootstrap.map(b => b.host + ':' + b.port)
console.log('Bootstrap:', bootstrapStr.join(', '))
check(testnet.nodes.length === 3, 'Testnet created with 3 nodes')
const allBootstrap = bootstrapStr.join(',')
// Spawn C++ server.
const serverBin = path.join(__dirname, 'test_interop_server_bin')
console.log('\nSpawning C++ server...')
const server = spawn(serverBin, [allBootstrap], {
stdio: ['pipe', 'pipe', 'pipe']
})
let serverStdout = ''
let serverPk = null
let serverReady = false
server.stderr.on('data', d => process.stderr.write('[server err] ' + d))
// Wait for server READY.
const serverReadyPromise = new Promise((resolve, reject) => {
const readyTimeout = setTimeout(() => {
reject(new Error('C++ server did not become READY within timeout'))
}, TIMEOUT_MS)
server.stdout.on('data', (d) => {
const s = d.toString()
serverStdout += s
for (const line of s.split('\n').filter(Boolean)) {
console.log('[server]', line)
if (line.startsWith('PUBLIC_KEY:')) {
serverPk = line.slice('PUBLIC_KEY:'.length).trim()
console.log(' → Parsed server PK:', serverPk.slice(0, 16) + '...')
}
if (line === 'READY') {
serverReady = true
clearTimeout(readyTimeout)
resolve()
}
}
})
server.on('error', (err) => {
clearTimeout(readyTimeout)
reject(err)
})
server.on('exit', (code) => {
clearTimeout(readyTimeout)
if (!serverReady) {
reject(new Error('C++ server exited before READY (code=' + code + ')'))
}
})
})
try {
await serverReadyPromise
check(serverReady, 'C++ server bootstrapped and listening')
check(serverPk !== null && serverPk.length === 64, 'C++ server public key parsed')
// Spawn C++ client.
const clientBin = path.join(__dirname, 'test_interop_client')
console.log('\nSpawning C++ client...')
console.log(' Target PK:', serverPk.slice(0, 16) + '...\n')
const client = spawn(clientBin, [allBootstrap, serverPk], {
stdio: ['pipe', 'pipe', 'pipe']
})
let clientStdout = ''
client.stdout.on('data', d => {
const s = d.toString()
clientStdout += s
for (const line of s.split('\n').filter(Boolean)) {
console.log('[client]', line)
}
})
client.stderr.on('data', d => process.stderr.write('[client err] ' + d))
// Wait for both processes.
const overallTimeout = setTimeout(() => {
console.log('\nTIMEOUT: killing both processes')
server.kill('SIGKILL')
client.kill('SIGKILL')
}, TIMEOUT_MS)
let clientExitCode = null
let serverExitCode = null
const clientDone = new Promise(resolve => {
client.on('exit', code => {
clientExitCode = code
console.log('\nclient exited with code', code)
// Client connected successfully — kill server.
if (code === 0) {
server.kill('SIGTERM')
}
resolve()
})
})
const serverDone = new Promise(resolve => {
server.on('exit', code => {
serverExitCode = code
console.log('server exited with code', code)
resolve()
})
})
await Promise.all([clientDone, serverDone])
clearTimeout(overallTimeout)
// Check results.
console.log('\n--- Results ---')
check(clientExitCode === 0, 'C++ client connected (exit 0)')
check(serverExitCode === 0 || serverExitCode === null, 'C++ server connected or was terminated')
check(clientStdout.includes('connected'), 'C++ client reported connection')
check(serverStdout.includes('CONNECTED') || serverStdout.includes('HANDSHAKE'), 'C++ server received handshake/connection')
} catch (err) {
console.error('\nTest error:', err.message)
check(false, 'Test completed without error')
try { server.kill('SIGKILL') } catch (_) {}
} finally {
await testnet.destroy()
}
console.log('\n=== Summary:', passed + '/' + (passed + failed), 'passed ===')
process.exit(failed > 0 ? 1 : 0)
}
main().catch((err) => {
console.error('Fatal:', err)
process.exit(1)
})