upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/integration/phase2.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration/phase2.mjs')
-rw-r--r--tests/integration/phase2.mjs151
1 files changed, 151 insertions, 0 deletions
diff --git a/tests/integration/phase2.mjs b/tests/integration/phase2.mjs
new file mode 100644
index 0000000..9eaa7d7
--- /dev/null
+++ b/tests/integration/phase2.mjs
@@ -0,0 +1,151 @@
1import { execSync } from 'child_process';
2
3const IP = process.env.TOLLGATE_IP || '10.192.45.1';
4const API = `http://${IP}:2121`;
5let passed = 0, failed = 0;
6
7function assert(condition, test) {
8 if (condition) { console.log(` ✓ ${test}`); passed++; }
9 else { console.log(` ✗ ${test}`); failed++; }
10}
11
12function curlBody(url, options = {}) {
13 const cmd = options.method
14 ? `curl -s --connect-timeout 5 --max-time 10 -X ${options.method} ${options.data ? `-d '${options.data.replace(/'/g, "'\\''")}'` : ''} "${url}"`
15 : `curl -s --connect-timeout 5 --max-time 10 "${url}"`;
16 try { return execSync(cmd, { encoding: 'utf8', timeout: 15000 }); }
17 catch { return null; }
18}
19
20function curlStatus(url, options = {}) {
21 const cmd = `curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 --max-time 10 ${options.method ? `-X ${options.method}` : ''} ${options.data ? `-d '${options.data.replace(/'/g, "'\\''")}'` : ''} "${url}"`;
22 try { return execSync(cmd, { encoding: 'utf8', timeout: 15000 }).trim(); }
23 catch { return null; }
24}
25
26async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
27
28console.log(`\n=== Phase 2 Tests (target: ${API}) ===\n`);
29
30// Test 15: Advertisement valid
31console.log('Test 15: GET :2121/ returns kind=10021 advertisement');
32const body15 = curlBody(`${API}/`);
33const json15 = body15 ? JSON.parse(body15) : null;
34assert(json15 && json15.kind === 10021, 'kind=10021');
35assert(json15 && json15.tags && json15.tags.some(t => t[0] === 'price_per_step'), 'Has price_per_step tag');
36assert(json15 && json15.tags && json15.tags.some(t => t[0] === 'step_size'), 'Has step_size tag');
37assert(json15 && json15.tags && json15.tags.some(t => t[0] === 'metric'), 'Has metric tag');
38
39// Test 19: Invalid token
40console.log('\nTest 19: POST :2121/ with invalid token');
41const body19 = curlBody(`${API}/`, { method: 'POST', data: 'garbage_not_a_token' });
42const json19 = body19 ? JSON.parse(body19) : null;
43assert(json19 && json19.kind === 21023, 'Returns kind=21023 notice');
44assert(json19 && json19.tags && json19.tags.some(t => t[0] === 'code'), 'Has error code tag');
45const status19 = curlStatus(`${API}/`, { method: 'POST', data: 'garbage_not_a_token' });
46assert(status19 === '400', 'Returns HTTP 400');
47
48// Test 21: Wrong mint (token from wrong mint)
49console.log('\nTest 21: POST :2121/ with wrong mint token');
50const wrongMintToken = 'cashuA' + Buffer.from(JSON.stringify({
51 token: [{ mint: 'https://wrong.mint.example.com', proofs: [{ amount: 21, secret: 'test', id: '00'.repeat(8), C: '02'.repeat(33) }] }]
52})).toString('base64url');
53const body21 = curlBody(`${API}/`, { method: 'POST', data: wrongMintToken });
54const json21 = body21 ? JSON.parse(body21) : null;
55assert(json21 && json21.kind === 21023, 'Returns kind=21023');
56const codeTag21 = json21 && json21.tags && json21.tags.find(t => t[0] === 'code');
57assert(codeTag21 && codeTag21[1] === 'payment-error-mint-not-accepted', 'Error code: mint-not-accepted');
58
59// Test valid token (if provided)
60const TEST_TOKEN = process.env.TEST_TOKEN;
61if (TEST_TOKEN) {
62 console.log('\nTest 16: POST :2121/ with valid token');
63 const body16 = curlBody(`${API}/`, { method: 'POST', data: TEST_TOKEN });
64 const json16 = body16 ? JSON.parse(body16) : null;
65 assert(json16 && json16.kind === 1022, 'Returns kind=1022 session');
66 assert(json16 && json16.tags && json16.tags.some(t => t[0] === 'allotment'), 'Has allotment tag');
67
68 // Test 17: Usage tracking
69 console.log('\nTest 17: GET :2121/usage after payment');
70 const body17 = curlBody(`${API}/usage`);
71 assert(body17 && !body17.includes('-1/-1'), 'Returns active usage');
72
73 // Test 18: Internet after payment
74 console.log('\nTest 18: Internet works after payment');
75 await sleep(1500);
76 const sudoPw = process.env.SUDO_PW || 'c03rad0r123';
77 try {
78 execSync(`echo '${sudoPw}' | sudo -S ip route add default via ${IP} dev wlp59s0 metric 50 2>/dev/null`, { encoding: 'utf8', timeout: 5000 });
79 } catch {}
80 let pingOk = false;
81 try {
82 const ping18 = execSync('ping -c 3 -W 3 8.8.8.8', { encoding: 'utf8', timeout: 15000 });
83 pingOk = ping18 && !ping18.includes('100% packet loss');
84 } catch {
85 pingOk = false;
86 }
87 assert(pingOk, 'Internet works');
88
89 // Test 20: Spent token
90 console.log('\nTest 20: Reuse token (should fail)');
91 const body20 = curlBody(`${API}/`, { method: 'POST', data: TEST_TOKEN });
92 const json20 = body20 ? JSON.parse(body20) : null;
93 assert(json20 && json20.kind === 21023, 'Returns kind=21023 for spent token');
94
95 // Test 22: Session expiry
96 console.log('\nTest 22: Session expiry (waiting 65s for allotment to expire)...');
97 try {
98 execSync(`echo '${sudoPw}' | sudo -S ip route add default via ${IP} dev wlp59s0 metric 50 2>/dev/null`, { encoding: 'utf8', timeout: 5000 });
99 } catch {}
100 await sleep(65000);
101 let expiredPingOk = true;
102 try {
103 const ping22 = execSync('ping -c 2 -W 2 8.8.8.8', { encoding: 'utf8', timeout: 10000 });
104 expiredPingOk = !ping22.includes('100% packet loss');
105 } catch {
106 expiredPingOk = false;
107 }
108 assert(!expiredPingOk, 'Internet blocked after session expiry');
109 const body22 = curlBody(`${API}/usage`);
110 assert(body22 && body22.includes('-1/-1'), 'Usage returns -1/-1 after expiry');
111
112 // Test 23: Session renewal
113 const TEST_TOKEN2 = process.env.TEST_TOKEN2;
114 if (TEST_TOKEN2) {
115 console.log('\nTest 23: Session renewal with second token');
116 const body23 = curlBody(`${API}/`, { method: 'POST', data: TEST_TOKEN2 });
117 const json23 = body23 ? JSON.parse(body23) : null;
118 assert(json23 && json23.kind === 1022, 'Returns kind=1022 for renewal');
119 await sleep(1500);
120 let renewPingOk = false;
121 try {
122 const ping23 = execSync('ping -c 2 -W 2 8.8.8.8', { encoding: 'utf8', timeout: 10000 });
123 renewPingOk = !ping23.includes('100% packet loss');
124 } catch {
125 renewPingOk = false;
126 }
127 assert(renewPingOk, 'Internet works after renewal');
128 } else {
129 console.log('\n ⚠ Skipping test 23: Set TEST_TOKEN2 env var for renewal test');
130 }
131 try {
132 execSync(`echo '${sudoPw}' | sudo -S ip route del default via ${IP} dev wlp59s0 metric 50 2>/dev/null`, { encoding: 'utf8', timeout: 5000 });
133 } catch {}
134} else {
135 console.log('\n ⚠ Skipping tests 16-20: Set TEST_TOKEN env var with a valid Cashu token');
136}
137
138// Test: whoami on :2121
139console.log('\nTest: GET :2121/whoami');
140const bodyWhoami = curlBody(`${API}/whoami`);
141assert(bodyWhoami && bodyWhoami.includes('mac='), '/whoami returns mac=...');
142
143// Test: Portal has payment form
144console.log('\nTest: Portal has payment form');
145const bodyPortal = curlBody(`http://${IP}/`);
146assert(bodyPortal && bodyPortal.includes('cashuA'), 'Portal has Cashu token input');
147assert(bodyPortal && bodyPortal.includes('Pay & Connect') || bodyPortal && bodyPortal.includes('Pay'), 'Portal has Pay button');
148
149// Summary
150console.log(`\n=== Phase 2 Results: ${passed} passed, ${failed} failed ===\n`);
151process.exit(failed > 0 ? 1 : 0);