diff options
Diffstat (limited to 'tests/phase2.mjs')
| -rw-r--r-- | tests/phase2.mjs | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/tests/phase2.mjs b/tests/phase2.mjs new file mode 100644 index 0000000..3136da3 --- /dev/null +++ b/tests/phase2.mjs | |||
| @@ -0,0 +1,101 @@ | |||
| 1 | import { execSync } from 'child_process'; | ||
| 2 | |||
| 3 | const IP = process.env.TOLLGATE_IP || '192.168.4.1'; | ||
| 4 | const API = `http://${IP}:2121`; | ||
| 5 | let passed = 0, failed = 0; | ||
| 6 | |||
| 7 | function assert(condition, test) { | ||
| 8 | if (condition) { console.log(` ✓ ${test}`); passed++; } | ||
| 9 | else { console.log(` ✗ ${test}`); failed++; } | ||
| 10 | } | ||
| 11 | |||
| 12 | function 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 | |||
| 20 | function 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 | |||
| 26 | async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } | ||
| 27 | |||
| 28 | console.log(`\n=== Phase 2 Tests (target: ${API}) ===\n`); | ||
| 29 | |||
| 30 | // Test 15: Advertisement valid | ||
| 31 | console.log('Test 15: GET :2121/ returns kind=10021 advertisement'); | ||
| 32 | const body15 = curlBody(`${API}/`); | ||
| 33 | const json15 = body15 ? JSON.parse(body15) : null; | ||
| 34 | assert(json15 && json15.kind === 10021, 'kind=10021'); | ||
| 35 | assert(json15 && json15.tags && json15.tags.some(t => t[0] === 'price_per_step'), 'Has price_per_step tag'); | ||
| 36 | assert(json15 && json15.tags && json15.tags.some(t => t[0] === 'step_size'), 'Has step_size tag'); | ||
| 37 | assert(json15 && json15.tags && json15.tags.some(t => t[0] === 'metric'), 'Has metric tag'); | ||
| 38 | |||
| 39 | // Test 19: Invalid token | ||
| 40 | console.log('\nTest 19: POST :2121/ with invalid token'); | ||
| 41 | const body19 = curlBody(`${API}/`, { method: 'POST', data: 'garbage_not_a_token' }); | ||
| 42 | const json19 = body19 ? JSON.parse(body19) : null; | ||
| 43 | assert(json19 && json19.kind === 21023, 'Returns kind=21023 notice'); | ||
| 44 | assert(json19 && json19.tags && json19.tags.some(t => t[0] === 'code'), 'Has error code tag'); | ||
| 45 | const status19 = curlStatus(`${API}/`, { method: 'POST', data: 'garbage_not_a_token' }); | ||
| 46 | assert(status19 === '400', 'Returns HTTP 400'); | ||
| 47 | |||
| 48 | // Test 21: Wrong mint (token from wrong mint) | ||
| 49 | console.log('\nTest 21: POST :2121/ with wrong mint token'); | ||
| 50 | const 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'); | ||
| 53 | const body21 = curlBody(`${API}/`, { method: 'POST', data: wrongMintToken }); | ||
| 54 | const json21 = body21 ? JSON.parse(body21) : null; | ||
| 55 | assert(json21 && json21.kind === 21023, 'Returns kind=21023'); | ||
| 56 | const codeTag21 = json21 && json21.tags && json21.tags.find(t => t[0] === 'code'); | ||
| 57 | assert(codeTag21 && codeTag21[1] === 'payment-error-mint-not-accepted', 'Error code: mint-not-accepted'); | ||
| 58 | |||
| 59 | // Test valid token (if provided) | ||
| 60 | const TEST_TOKEN = process.env.TEST_TOKEN; | ||
| 61 | if (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(1000); | ||
| 76 | const ping18 = execSync('ping -c 2 -W 2 -I wlp59s0 8.8.8.8', { encoding: 'utf8', timeout: 10000 }); | ||
| 77 | assert(ping18 && !ping18.includes('100% packet loss'), 'Internet works'); | ||
| 78 | |||
| 79 | // Test 20: Spent token | ||
| 80 | console.log('\nTest 20: Reuse token (should fail)'); | ||
| 81 | const body20 = curlBody(`${API}/`, { method: 'POST', data: TEST_TOKEN }); | ||
| 82 | const json20 = body20 ? JSON.parse(body20) : null; | ||
| 83 | assert(json20 && json20.kind === 21023, 'Returns kind=21023 for spent token'); | ||
| 84 | } else { | ||
| 85 | console.log('\n ⚠ Skipping tests 16-20: Set TEST_TOKEN env var with a valid Cashu token'); | ||
| 86 | } | ||
| 87 | |||
| 88 | // Test: whoami on :2121 | ||
| 89 | console.log('\nTest: GET :2121/whoami'); | ||
| 90 | const bodyWhoami = curlBody(`${API}/whoami`); | ||
| 91 | assert(bodyWhoami && bodyWhoami.startsWith('mac='), '/whoami returns mac=...'); | ||
| 92 | |||
| 93 | // Test: Portal has payment form | ||
| 94 | console.log('\nTest: Portal has payment form'); | ||
| 95 | const bodyPortal = curlBody(`http://${IP}/`); | ||
| 96 | assert(bodyPortal && bodyPortal.includes('cashuA'), 'Portal has Cashu token input'); | ||
| 97 | assert(bodyPortal && bodyPortal.includes('Pay & Connect') || bodyPortal && bodyPortal.includes('Pay'), 'Portal has Pay button'); | ||
| 98 | |||
| 99 | // Summary | ||
| 100 | console.log(`\n=== Phase 2 Results: ${passed} passed, ${failed} failed ===\n`); | ||
| 101 | process.exit(failed > 0 ? 1 : 0); | ||