upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/tests/integration/test-cvm-roundtrip.mjs
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-19 04:10:12 +0530
committerYour Name <you@example.com>2026-05-19 04:10:12 +0530
commit2d78aadfd603fab9a9342b1281ad1d46ad82cf1d (patch)
tree3e8875b7e0301ac6634548e186542e2d67a68f34 /tests/integration/test-cvm-roundtrip.mjs
parentabee221b0f0e5a4513ab126afbdfddc2728df6be (diff)
feat: relay hardening — restore build, add tests, negentropy adapter
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
Diffstat (limited to 'tests/integration/test-cvm-roundtrip.mjs')
-rw-r--r--tests/integration/test-cvm-roundtrip.mjs175
1 files changed, 175 insertions, 0 deletions
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 @@
1import { execSync } from 'child_process';
2import WebSocket from 'ws';
3
4const IP = process.env.TOLLGATE_IP || '10.192.45.1';
5const CVM_RELAY = process.env.CVM_RELAY || 'wss://relay.primal.net';
6const NSEC = process.env.CVM_NSEC || 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2';
7
8let passed = 0, failed = 0;
9
10function assert(condition, test) {
11 if (condition) { console.log(` \u2713 ${test}`); passed++; }
12 else { console.log(` \u2717 ${test}`); failed++; }
13}
14
15function nak(args, timeout = 10000) {
16 try {
17 return execSync(`timeout ${timeout / 1000} nak ${args}`, {
18 encoding: 'utf8',
19 stdio: ['pipe', 'pipe', 'pipe'],
20 timeout
21 }).trim();
22 } catch (e) {
23 return e.stdout ? e.stdout.trim() : '';
24 }
25}
26
27function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
28
29function connectWSS(url) {
30 return new Promise((resolve, reject) => {
31 const ws = new WebSocket(url);
32 const timer = setTimeout(() => { ws.close(); reject(new Error('connect timeout')); }, 10000);
33 ws.on('open', () => { clearTimeout(timer); resolve(ws); });
34 ws.on('error', (e) => { clearTimeout(timer); reject(e); });
35 });
36}
37
38function collectMessages(ws, count, timeoutMs = 15000) {
39 return new Promise((resolve) => {
40 const msgs = [];
41 const timer = setTimeout(() => resolve(msgs), timeoutMs);
42 ws.on('message', (data) => {
43 try { msgs.push(JSON.parse(data.toString())); } catch { msgs.push(data.toString()); }
44 if (msgs.length >= count) { clearTimeout(timer); resolve(msgs); }
45 });
46 ws.on('error', () => { clearTimeout(timer); resolve(msgs); });
47 ws.on('close', () => { clearTimeout(timer); resolve(msgs); });
48 });
49}
50
51async function runTests() {
52 console.log(`\n=== CVM MCP Roundtrip Tests (target: ${IP}) ===\n`);
53
54 const npub = nak(`key public ${NSEC}`);
55 console.log(`Board npub: ${npub}`);
56 assert(npub.length === 64, 'npub hex is 64 chars');
57
58 console.log('\n--- Test 1: Board API reachable ---');
59 try {
60 const result = execSync(`curl -s --connect-timeout 5 http://${IP}:2121/usage`, { encoding: 'utf8', timeout: 5000 });
61 assert(result.length > 0, 'API /usage responds');
62 } catch (e) {
63 assert(false, `API /usage reachable — ${e.message}`);
64 console.log('\n Board not reachable — skipping remaining tests');
65 console.log(`\n=== Results: ${passed} passed, ${failed} failed ===\n`);
66 process.exit(failed > 0 ? 1 : 0);
67 }
68
69 console.log('\n--- Test 2: Kind 11316 announcement exists on relay ---');
70 const ann11316 = nak(`req -k 11316 -a ${npub} -l 1 ${CVM_RELAY}`, 8000);
71 if (ann11316.includes('"kind"') || ann11316.includes('11316')) {
72 assert(true, `Kind 11316 found on ${CVM_RELAY}`);
73 if (ann11316.includes('TollGate')) {
74 assert(true, 'Announcement contains "TollGate"');
75 }
76 } else {
77 console.log(` (no 11316 from ${CVM_RELAY} — may not have been published yet)`);
78 }
79
80 console.log('\n--- Test 3: Kind 11317 tools list exists on relay ---');
81 const ann11317 = nak(`req -k 11317 -a ${npub} -l 1 ${CVM_RELAY}`, 8000);
82 if (ann11317.includes('"kind"') || ann11317.includes('11317')) {
83 assert(true, `Kind 11317 found on ${CVM_RELAY}`);
84 const hasTools = ann11317.includes('get_config') || ann11317.includes('tools');
85 assert(hasTools, 'Tools list contains expected tool names');
86 } else {
87 console.log(` (no 11317 from ${CVM_RELAY} — may not have been published yet)`);
88 }
89
90 console.log('\n--- Test 4: Kind 10002 relay list exists ---');
91 const ann10002 = nak(`req -k 10002 -a ${npub} -l 1 ${CVM_RELAY}`, 8000);
92 if (ann10002.includes('"kind"') || ann10002.includes('10002')) {
93 assert(true, `Kind 10002 found on ${CVM_RELAY}`);
94 } else {
95 console.log(` (no 10002 from ${CVM_RELAY})`);
96 }
97
98 console.log('\n--- Test 5: MCP get_config roundtrip via public relay ---');
99 try {
100 const content = JSON.stringify({
101 jsonrpc: '2.0',
102 id: Date.now(),
103 method: 'tools/call',
104 params: { name: 'get_config', arguments: {} }
105 });
106
107 const eventOut = nak(`event --kind 25910 --tag p=${npub} --content '${content.replace(/'/g, "'\\''")}' ${CVM_RELAY}`, 8000);
108 const published = eventOut.includes('Success') || eventOut.includes('"id"');
109 assert(published, `Published kind 25910 get_config to ${CVM_RELAY}`);
110
111 if (published) {
112 console.log(' Waiting 8s for board to process and respond...');
113 await sleep(8000);
114
115 const resp = nak(`req -k 25910 -a ${npub} -l 5 ${CVM_RELAY}`, 8000);
116 const hasResponse = resp.includes('"kind"') && resp.includes('25910');
117 assert(hasResponse, 'Received kind 25910 response from board');
118
119 if (hasResponse) {
120 try {
121 const lines = resp.split('\n').filter(l => l.includes('"kind"'));
122 for (const line of lines) {
123 const evt = JSON.parse(line);
124 if (evt.kind === 25910 && evt.content) {
125 try {
126 const mcpr = JSON.parse(evt.content);
127 assert(mcpr.result !== undefined || mcpr.error !== undefined, 'Response has MCP result or error');
128 } catch {
129 assert(evt.content.length > 0, 'Response content is non-empty');
130 }
131 break;
132 }
133 }
134 } catch {
135 assert(resp.length > 0, 'Raw response data received');
136 }
137 }
138 }
139 } catch (e) {
140 assert(false, `MCP roundtrip — ${e.message}`);
141 }
142
143 console.log('\n--- Test 6: MCP get_balance roundtrip via public relay ---');
144 try {
145 const content = JSON.stringify({
146 jsonrpc: '2.0',
147 id: Date.now(),
148 method: 'tools/call',
149 params: { name: 'get_balance', arguments: {} }
150 });
151
152 const eventOut = nak(`event --kind 25910 --tag p=${npub} --content '${content.replace(/'/g, "'\\''")}' ${CVM_RELAY}`, 8000);
153 const published = eventOut.includes('Success') || eventOut.includes('"id"');
154 assert(published, `Published kind 25910 get_balance to ${CVM_RELAY}`);
155
156 if (published) {
157 console.log(' Waiting 8s for board to process and respond...');
158 await sleep(8000);
159
160 const resp = nak(`req -k 25910 -a ${npub} -l 10 ${CVM_RELAY}`, 8000);
161 const hasBalance = resp.includes('balance') || resp.includes('get_balance') || resp.includes('"kind"');
162 assert(hasBalance, 'Received balance response');
163 }
164 } catch (e) {
165 assert(false, `get_balance roundtrip — ${e.message}`);
166 }
167
168 console.log(`\n=== Results: ${passed} passed, ${failed} failed ===\n`);
169 process.exit(failed > 0 ? 1 : 0);
170}
171
172runTests().catch(e => {
173 console.error('Test error:', e.message);
174 process.exit(1);
175});