From 2d78aadfd603fab9a9342b1281ad1d46ad82cf1d Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 19 May 2026 04:10:12 +0530 Subject: feat: relay hardening — restore build, add tests, negentropy adapter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restores build broken by eeb9d2d (cvm-relay-stability removed deps): - CMakeLists.txt: restore display.c, font.c, local_relay.c, relay_selector.c, sync_manager.c, axs15231b, qrcode, wisp_relay - tollgate_main.c: restore display.h, local_relay.h, relay_selector.h, sync_manager.h includes and display calls - cvm_server.c: kept master's keepalive/timeout/ping-pong fixes New test infrastructure: - test-local-relay, test-relay-nip11, test-cvm-roundtrip, test-cvm-mcp, test-cross-board make targets - test-cvm-roundtrip.mjs: MCP get_config + get_balance via public relay - test-cross-board.mjs: cross-board payment test - test-cvm-mcp-relay.mjs: kept from master New unit tests (35 tests): - test_display.c: 22 tests for escape_wifi_field - test_negentropy_adapter.c: 13 tests for negentropy adapter New modules: - negentropy_adapter.c/h: NIP-77 adapter skeleton Docs: - AGENTS.md: display module docs, new test commands - RELAY_HARDENING_PLAN.md: hardening checklist - RELAY_HARDENING_MERGE.md: merge plan and checklist Cleanup: - Removed CHECKLIST-CVM-RELAY.md, PLAN-SQUASH-MERGE.md (stale planning docs) - Removed components/esp-miner submodule Host unit tests: 63/63 pass --- tests/integration/test-cvm-roundtrip.mjs | 175 +++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 tests/integration/test-cvm-roundtrip.mjs (limited to 'tests/integration/test-cvm-roundtrip.mjs') diff --git a/tests/integration/test-cvm-roundtrip.mjs b/tests/integration/test-cvm-roundtrip.mjs new file mode 100644 index 0000000..821cfe7 --- /dev/null +++ b/tests/integration/test-cvm-roundtrip.mjs @@ -0,0 +1,175 @@ +import { execSync } from 'child_process'; +import WebSocket from 'ws'; + +const IP = process.env.TOLLGATE_IP || '10.192.45.1'; +const CVM_RELAY = process.env.CVM_RELAY || 'wss://relay.primal.net'; +const NSEC = process.env.CVM_NSEC || 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2'; + +let passed = 0, failed = 0; + +function assert(condition, test) { + if (condition) { console.log(` \u2713 ${test}`); passed++; } + else { console.log(` \u2717 ${test}`); failed++; } +} + +function nak(args, timeout = 10000) { + try { + return execSync(`timeout ${timeout / 1000} nak ${args}`, { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'], + timeout + }).trim(); + } catch (e) { + return e.stdout ? e.stdout.trim() : ''; + } +} + +function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } + +function connectWSS(url) { + return new Promise((resolve, reject) => { + const ws = new WebSocket(url); + const timer = setTimeout(() => { ws.close(); reject(new Error('connect timeout')); }, 10000); + ws.on('open', () => { clearTimeout(timer); resolve(ws); }); + ws.on('error', (e) => { clearTimeout(timer); reject(e); }); + }); +} + +function collectMessages(ws, count, timeoutMs = 15000) { + return new Promise((resolve) => { + const msgs = []; + const timer = setTimeout(() => resolve(msgs), timeoutMs); + ws.on('message', (data) => { + try { msgs.push(JSON.parse(data.toString())); } catch { msgs.push(data.toString()); } + if (msgs.length >= count) { clearTimeout(timer); resolve(msgs); } + }); + ws.on('error', () => { clearTimeout(timer); resolve(msgs); }); + ws.on('close', () => { clearTimeout(timer); resolve(msgs); }); + }); +} + +async function runTests() { + console.log(`\n=== CVM MCP Roundtrip Tests (target: ${IP}) ===\n`); + + const npub = nak(`key public ${NSEC}`); + console.log(`Board npub: ${npub}`); + assert(npub.length === 64, 'npub hex is 64 chars'); + + console.log('\n--- Test 1: Board API reachable ---'); + try { + const result = execSync(`curl -s --connect-timeout 5 http://${IP}:2121/usage`, { encoding: 'utf8', timeout: 5000 }); + assert(result.length > 0, 'API /usage responds'); + } catch (e) { + assert(false, `API /usage reachable — ${e.message}`); + console.log('\n Board not reachable — skipping remaining tests'); + console.log(`\n=== Results: ${passed} passed, ${failed} failed ===\n`); + process.exit(failed > 0 ? 1 : 0); + } + + console.log('\n--- Test 2: Kind 11316 announcement exists on relay ---'); + const ann11316 = nak(`req -k 11316 -a ${npub} -l 1 ${CVM_RELAY}`, 8000); + if (ann11316.includes('"kind"') || ann11316.includes('11316')) { + assert(true, `Kind 11316 found on ${CVM_RELAY}`); + if (ann11316.includes('TollGate')) { + assert(true, 'Announcement contains "TollGate"'); + } + } else { + console.log(` (no 11316 from ${CVM_RELAY} — may not have been published yet)`); + } + + console.log('\n--- Test 3: Kind 11317 tools list exists on relay ---'); + const ann11317 = nak(`req -k 11317 -a ${npub} -l 1 ${CVM_RELAY}`, 8000); + if (ann11317.includes('"kind"') || ann11317.includes('11317')) { + assert(true, `Kind 11317 found on ${CVM_RELAY}`); + const hasTools = ann11317.includes('get_config') || ann11317.includes('tools'); + assert(hasTools, 'Tools list contains expected tool names'); + } else { + console.log(` (no 11317 from ${CVM_RELAY} — may not have been published yet)`); + } + + console.log('\n--- Test 4: Kind 10002 relay list exists ---'); + const ann10002 = nak(`req -k 10002 -a ${npub} -l 1 ${CVM_RELAY}`, 8000); + if (ann10002.includes('"kind"') || ann10002.includes('10002')) { + assert(true, `Kind 10002 found on ${CVM_RELAY}`); + } else { + console.log(` (no 10002 from ${CVM_RELAY})`); + } + + console.log('\n--- Test 5: MCP get_config roundtrip via public relay ---'); + try { + const content = JSON.stringify({ + jsonrpc: '2.0', + id: Date.now(), + method: 'tools/call', + params: { name: 'get_config', arguments: {} } + }); + + const eventOut = nak(`event --kind 25910 --tag p=${npub} --content '${content.replace(/'/g, "'\\''")}' ${CVM_RELAY}`, 8000); + const published = eventOut.includes('Success') || eventOut.includes('"id"'); + assert(published, `Published kind 25910 get_config to ${CVM_RELAY}`); + + if (published) { + console.log(' Waiting 8s for board to process and respond...'); + await sleep(8000); + + const resp = nak(`req -k 25910 -a ${npub} -l 5 ${CVM_RELAY}`, 8000); + const hasResponse = resp.includes('"kind"') && resp.includes('25910'); + assert(hasResponse, 'Received kind 25910 response from board'); + + if (hasResponse) { + try { + const lines = resp.split('\n').filter(l => l.includes('"kind"')); + for (const line of lines) { + const evt = JSON.parse(line); + if (evt.kind === 25910 && evt.content) { + try { + const mcpr = JSON.parse(evt.content); + assert(mcpr.result !== undefined || mcpr.error !== undefined, 'Response has MCP result or error'); + } catch { + assert(evt.content.length > 0, 'Response content is non-empty'); + } + break; + } + } + } catch { + assert(resp.length > 0, 'Raw response data received'); + } + } + } + } catch (e) { + assert(false, `MCP roundtrip — ${e.message}`); + } + + console.log('\n--- Test 6: MCP get_balance roundtrip via public relay ---'); + try { + const content = JSON.stringify({ + jsonrpc: '2.0', + id: Date.now(), + method: 'tools/call', + params: { name: 'get_balance', arguments: {} } + }); + + const eventOut = nak(`event --kind 25910 --tag p=${npub} --content '${content.replace(/'/g, "'\\''")}' ${CVM_RELAY}`, 8000); + const published = eventOut.includes('Success') || eventOut.includes('"id"'); + assert(published, `Published kind 25910 get_balance to ${CVM_RELAY}`); + + if (published) { + console.log(' Waiting 8s for board to process and respond...'); + await sleep(8000); + + const resp = nak(`req -k 25910 -a ${npub} -l 10 ${CVM_RELAY}`, 8000); + const hasBalance = resp.includes('balance') || resp.includes('get_balance') || resp.includes('"kind"'); + assert(hasBalance, 'Received balance response'); + } + } catch (e) { + assert(false, `get_balance roundtrip — ${e.message}`); + } + + console.log(`\n=== Results: ${passed} passed, ${failed} failed ===\n`); + process.exit(failed > 0 ? 1 : 0); +} + +runTests().catch(e => { + console.error('Test error:', e.message); + process.exit(1); +}); -- cgit v1.2.3