From 42902a36bc52e009a1e8d3c371741e30a9cb4c33 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 19 May 2026 01:10:06 +0530 Subject: feat: ContextVM (MCP over Nostr) server with full integration Complete CVM implementation: persistent WebSocket relay listener, kind 25910 event subscription, MCP protocol handlers, CEP-6 announcements, 10 MCP tools, per-board hardware locks, WiFi EU regulatory fix. Architecture: - cvm_server.c: WS relay listener, kind 25910 subscription, MCP dispatch - mcp_handler.c/h: 10 MCP tools (get_config, set_config, get_balance, wallet_send, get_sessions, get_usage, set_payout, set_metric, set_price, wallet_melt) - Responses published via existing WS connection (not new TLS) - Auth check: only owner npub accepted - CEP-6: kinds 11316 (server), 11317 (tools), 10002 (relay list) - WS ping/pong keepalive every 30s, 60s TLS read timeout Critical fixes: - WiFi country code DE (ESP-IDF defaults to CN, breaks EU APs) - Subscription #p filter must be array not string - Use-after-free: tags_str freed before nostr_event_to_json - MCP responses via existing WS (ESP32 can't open multiple TLS) - EVENT msg buffer underflow, WS frame masking, TLS write loop Per-board hardware locks: - Lock files in physical-router-test-automation/locks/ - lock-a/b/c, unlock-a/b/c targets in 3 Makefiles - All hardware-touching targets require board lock Verified on Board B via relay.primal.net: - 282 unit tests passing (61 CVM + 60 MCP + 161 existing) - MCP initialize roundtrip: PASS - tools/list: PASS - tools/call get_config: PASS - tools/call get_balance: PASS - tools/call set_price: PASS (write operation) - CEP-6 announcements (11316, 11317, 10002): all accepted by relay - WiFi STA connection (EnterSSID-2.4GHz): PASS with country code DE - Board A WiFi confirmed hardware issue (not firmware) --- tests/integration/test-cvm.mjs | 94 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/integration/test-cvm.mjs (limited to 'tests/integration') diff --git a/tests/integration/test-cvm.mjs b/tests/integration/test-cvm.mjs new file mode 100644 index 0000000..8deb6ec --- /dev/null +++ b/tests/integration/test-cvm.mjs @@ -0,0 +1,94 @@ +import { execSync } from 'child_process'; + +const IP = process.env.TOLLGATE_IP || '10.192.45.1'; +const RELAYS = ['wss://relay.damus.io', 'wss://nos.lol']; + +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)); } + +async function runTests() { + console.log(`\n=== CVM Integration Tests (target: ${IP}) ===\n`); + + const npub = nak(`key public a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2`); + const npubHex = npub.trim(); + console.log(`Board npub: ${npubHex}`); + + const npubBech32 = nak(`encode npub ${npubHex}`).trim(); + console.log(`Board npub (bech32): ${npubBech32}`); + + assert(npubHex.length === 64, 'npub hex is 64 chars'); + + console.log('\n--- Test: Kind 11316 server announcement ---'); + for (const relay of RELAYS) { + console.log(` Querying ${relay}...`); + const result = nak(`req -k 11316 -a ${npubHex} -l 1 ${relay}`, 8000); + if (result.length > 0) { + assert(result.includes('"kind"') || result.includes('11316'), + `Kind 11316 found on ${relay}`); + if (result.includes('TollGate')) { + assert(true, `Announcement contains "TollGate"`); + } + } else { + console.log(` (no result from ${relay} — relay may be offline)`); + } + } + + console.log('\n--- Test: Kind 11317 tools list ---'); + for (const relay of RELAYS) { + const result = nak(`req -k 11317 -a ${npubHex} -l 1 ${relay}`, 8000); + if (result.length > 0) { + assert(result.includes('"kind"') || result.includes('11317'), + `Kind 11317 found on ${relay}`); + if (result.includes('get_config') && result.includes('wallet_melt')) { + assert(true, `Tools list has expected tools`); + } + } else { + console.log(` (no result from ${relay} — relay may be offline)`); + } + } + + console.log('\n--- Test: Kind 10002 relay list ---'); + for (const relay of RELAYS) { + const result = nak(`req -k 10002 -a ${npubHex} -l 1 ${relay}`, 8000); + if (result.length > 0) { + assert(result.includes('"kind"') || result.includes('10002'), + `Kind 10002 found on ${relay}`); + } else { + console.log(` (no result from ${relay} — relay may be offline)`); + } + } + + console.log('\n--- Test: API get_config (control check) ---'); + try { + const apiResult = execSync(`curl -s http://${IP}:2121/usage`, { encoding: 'utf8', timeout: 5000 }); + assert(apiResult.length > 0, 'API /usage responds (board is reachable)'); + } catch (e) { + console.log(' (API not reachable — board may be offline or not flashed yet)'); + } + + 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