upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/phase2.mjs
blob: 3136da312194ac40244184e7686cccb3fd511b55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { execSync } from 'child_process';

const IP = process.env.TOLLGATE_IP || '192.168.4.1';
const API = `http://${IP}:2121`;
let passed = 0, failed = 0;

function assert(condition, test) {
  if (condition) { console.log(`  ✓ ${test}`); passed++; }
  else { console.log(`  ✗ ${test}`); failed++; }
}

function curlBody(url, options = {}) {
  const cmd = options.method
    ? `curl -s --connect-timeout 5 --max-time 10 -X ${options.method} ${options.data ? `-d '${options.data.replace(/'/g, "'\\''")}'` : ''} "${url}"`
    : `curl -s --connect-timeout 5 --max-time 10 "${url}"`;
  try { return execSync(cmd, { encoding: 'utf8', timeout: 15000 }); }
  catch { return null; }
}

function curlStatus(url, options = {}) {
  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}"`;
  try { return execSync(cmd, { encoding: 'utf8', timeout: 15000 }).trim(); }
  catch { return null; }
}

async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

console.log(`\n=== Phase 2 Tests (target: ${API}) ===\n`);

// Test 15: Advertisement valid
console.log('Test 15: GET :2121/ returns kind=10021 advertisement');
const body15 = curlBody(`${API}/`);
const json15 = body15 ? JSON.parse(body15) : null;
assert(json15 && json15.kind === 10021, 'kind=10021');
assert(json15 && json15.tags && json15.tags.some(t => t[0] === 'price_per_step'), 'Has price_per_step tag');
assert(json15 && json15.tags && json15.tags.some(t => t[0] === 'step_size'), 'Has step_size tag');
assert(json15 && json15.tags && json15.tags.some(t => t[0] === 'metric'), 'Has metric tag');

// Test 19: Invalid token
console.log('\nTest 19: POST :2121/ with invalid token');
const body19 = curlBody(`${API}/`, { method: 'POST', data: 'garbage_not_a_token' });
const json19 = body19 ? JSON.parse(body19) : null;
assert(json19 && json19.kind === 21023, 'Returns kind=21023 notice');
assert(json19 && json19.tags && json19.tags.some(t => t[0] === 'code'), 'Has error code tag');
const status19 = curlStatus(`${API}/`, { method: 'POST', data: 'garbage_not_a_token' });
assert(status19 === '400', 'Returns HTTP 400');

// Test 21: Wrong mint (token from wrong mint)
console.log('\nTest 21: POST :2121/ with wrong mint token');
const wrongMintToken = 'cashuA' + Buffer.from(JSON.stringify({
  token: [{ mint: 'https://wrong.mint.example.com', proofs: [{ amount: 21, secret: 'test', id: '00'.repeat(8), C: '02'.repeat(33) }] }]
})).toString('base64url');
const body21 = curlBody(`${API}/`, { method: 'POST', data: wrongMintToken });
const json21 = body21 ? JSON.parse(body21) : null;
assert(json21 && json21.kind === 21023, 'Returns kind=21023');
const codeTag21 = json21 && json21.tags && json21.tags.find(t => t[0] === 'code');
assert(codeTag21 && codeTag21[1] === 'payment-error-mint-not-accepted', 'Error code: mint-not-accepted');

// Test valid token (if provided)
const TEST_TOKEN = process.env.TEST_TOKEN;
if (TEST_TOKEN) {
  console.log('\nTest 16: POST :2121/ with valid token');
  const body16 = curlBody(`${API}/`, { method: 'POST', data: TEST_TOKEN });
  const json16 = body16 ? JSON.parse(body16) : null;
  assert(json16 && json16.kind === 1022, 'Returns kind=1022 session');
  assert(json16 && json16.tags && json16.tags.some(t => t[0] === 'allotment'), 'Has allotment tag');

  // Test 17: Usage tracking
  console.log('\nTest 17: GET :2121/usage after payment');
  const body17 = curlBody(`${API}/usage`);
  assert(body17 && !body17.includes('-1/-1'), 'Returns active usage');

  // Test 18: Internet after payment
  console.log('\nTest 18: Internet works after payment');
  await sleep(1000);
  const ping18 = execSync('ping -c 2 -W 2 -I wlp59s0 8.8.8.8', { encoding: 'utf8', timeout: 10000 });
  assert(ping18 && !ping18.includes('100% packet loss'), 'Internet works');

  // Test 20: Spent token
  console.log('\nTest 20: Reuse token (should fail)');
  const body20 = curlBody(`${API}/`, { method: 'POST', data: TEST_TOKEN });
  const json20 = body20 ? JSON.parse(body20) : null;
  assert(json20 && json20.kind === 21023, 'Returns kind=21023 for spent token');
} else {
  console.log('\n  ⚠ Skipping tests 16-20: Set TEST_TOKEN env var with a valid Cashu token');
}

// Test: whoami on :2121
console.log('\nTest: GET :2121/whoami');
const bodyWhoami = curlBody(`${API}/whoami`);
assert(bodyWhoami && bodyWhoami.startsWith('mac='), '/whoami returns mac=...');

// Test: Portal has payment form
console.log('\nTest: Portal has payment form');
const bodyPortal = curlBody(`http://${IP}/`);
assert(bodyPortal && bodyPortal.includes('cashuA'), 'Portal has Cashu token input');
assert(bodyPortal && bodyPortal.includes('Pay & Connect') || bodyPortal && bodyPortal.includes('Pay'), 'Portal has Pay button');

// Summary
console.log(`\n=== Phase 2 Results: ${passed} passed, ${failed} failed ===\n`);
process.exit(failed > 0 ? 1 : 0);