diff options
Diffstat (limited to 'tests/integration/test-price-discovery.mjs')
| -rw-r--r-- | tests/integration/test-price-discovery.mjs | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/tests/integration/test-price-discovery.mjs b/tests/integration/test-price-discovery.mjs new file mode 100644 index 0000000..6762130 --- /dev/null +++ b/tests/integration/test-price-discovery.mjs | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | import { execSync } from 'child_process'; | ||
| 2 | |||
| 3 | const BOARD_A_IP = process.env.TOLLGATE_IP || '10.185.47.1'; | ||
| 4 | const BOARD_B_IP = process.env.TOLLGATE_B_IP || process.env.TOLLGATE_IP_B || '10.192.45.1'; | ||
| 5 | const API_A = `http://${BOARD_A_IP}:2121`; | ||
| 6 | const API_B = `http://${BOARD_B_IP}:2121`; | ||
| 7 | |||
| 8 | function run(cmd) { | ||
| 9 | try { return execSync(cmd, { encoding: 'utf8', timeout: 15000 }); } | ||
| 10 | catch (e) { return e.stdout || null; } | ||
| 11 | } | ||
| 12 | |||
| 13 | function runJson(cmd) { | ||
| 14 | const out = run(cmd); | ||
| 15 | try { return out ? JSON.parse(out) : null; } | ||
| 16 | catch { return null; } | ||
| 17 | } | ||
| 18 | |||
| 19 | let passed = 0, failed = 0; | ||
| 20 | function assert(cond, msg) { | ||
| 21 | if (cond) { console.log(` PASS: ${msg}`); passed++; } | ||
| 22 | else { console.log(` FAIL: ${msg}`); failed++; } | ||
| 23 | } | ||
| 24 | |||
| 25 | function canReach(url) { | ||
| 26 | const result = run(`curl -s --connect-timeout 3 --max-time 5 -o /dev/null -w "%{http_code}" ${url}`); | ||
| 27 | return result && result.trim() !== '000' && result.trim() !== ''; | ||
| 28 | } | ||
| 29 | |||
| 30 | console.log('=== test-price-discovery (two-board) ===\n'); | ||
| 31 | |||
| 32 | const reachA = canReach(`${API_A}/market`); | ||
| 33 | const reachB = canReach(`${API_B}/market`); | ||
| 34 | |||
| 35 | console.log(`Reachability: Board A=${reachA ? 'YES' : 'NO'}, Board B=${reachB ? 'YES' : 'NO'}\n`); | ||
| 36 | |||
| 37 | if (!reachA && !reachB) { | ||
| 38 | console.log('FATAL: Neither board reachable. Check TOLLGATE_IP and TOLLGATE_B_IP'); | ||
| 39 | process.exit(1); | ||
| 40 | } | ||
| 41 | |||
| 42 | console.log('--- Board A: market endpoint ---'); | ||
| 43 | { | ||
| 44 | if (reachA) { | ||
| 45 | const data = runJson(`curl -s --connect-timeout 5 --max-time 10 ${API_A}/market`); | ||
| 46 | assert(data !== null, 'Board A /market returns JSON'); | ||
| 47 | assert(typeof data?.count === 'number', `Board A count is ${data?.count}`); | ||
| 48 | if (data && data.entries) { | ||
| 49 | console.log(` Board A sees ${data.count} nearby TollGate(s):`); | ||
| 50 | for (const e of data.entries) { | ||
| 51 | console.log(` ${e.ssid} (BSSID: ${e.bssid}) — ${e.price_per_step} sats/step, RSSI: ${e.rssi}`); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } else { | ||
| 55 | console.log(' SKIP: Board A not reachable'); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | console.log('\n--- Board B: market endpoint ---'); | ||
| 60 | { | ||
| 61 | if (reachB) { | ||
| 62 | const data = runJson(`curl -s --connect-timeout 5 --max-time 10 ${API_B}/market`); | ||
| 63 | assert(data !== null, 'Board B /market returns JSON'); | ||
| 64 | assert(typeof data?.count === 'number', `Board B count is ${data?.count}`); | ||
| 65 | if (data && data.entries) { | ||
| 66 | console.log(` Board B sees ${data.count} nearby TollGate(s):`); | ||
| 67 | for (const e of data.entries) { | ||
| 68 | console.log(` ${e.ssid} (BSSID: ${e.bssid}) — ${e.price_per_step} sats/step, RSSI: ${e.rssi}`); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } else { | ||
| 72 | console.log(' SKIP: Board B not reachable'); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | console.log('\n--- Cross-discovery: Board A sees Board B ---'); | ||
| 77 | { | ||
| 78 | if (reachA) { | ||
| 79 | const mktA = runJson(`curl -s --connect-timeout 5 --max-time 10 ${API_A}/market`); | ||
| 80 | if (mktA && mktA.count > 0) { | ||
| 81 | const foundB = mktA.entries.some(e => | ||
| 82 | e.ssid.startsWith('TollGate-') && e.bssid !== '' && e.price_per_step > 0 | ||
| 83 | ); | ||
| 84 | assert(foundB, `Board A discovered another TollGate (count=${mktA.count})`); | ||
| 85 | } else { | ||
| 86 | console.log(' INFO: Board A has 0 entries. Scan may need more time.'); | ||
| 87 | } | ||
| 88 | } else { | ||
| 89 | console.log(' SKIP: Board A not reachable'); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | console.log('\n--- Cross-discovery: Board B sees Board A ---'); | ||
| 94 | { | ||
| 95 | if (reachB) { | ||
| 96 | const mktB = runJson(`curl -s --connect-timeout 5 --max-time 10 ${API_B}/market`); | ||
| 97 | if (mktB && mktB.count > 0) { | ||
| 98 | const foundA = mktB.entries.some(e => | ||
| 99 | e.ssid.startsWith('TollGate-') && e.bssid !== '' && e.price_per_step > 0 | ||
| 100 | ); | ||
| 101 | assert(foundA, `Board B discovered another TollGate (count=${mktB.count})`); | ||
| 102 | } else { | ||
| 103 | console.log(' INFO: Board B has 0 entries. Scan may need more time.'); | ||
| 104 | } | ||
| 105 | } else { | ||
| 106 | console.log(' SKIP: Board B not reachable'); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | console.log('\n--- Discovery data integrity ---'); | ||
| 111 | { | ||
| 112 | const boards = []; | ||
| 113 | if (reachA) { | ||
| 114 | const mktA = runJson(`curl -s --connect-timeout 5 --max-time 10 ${API_A}/market`); | ||
| 115 | if (mktA?.entries) boards.push({ name: 'A', data: mktA }); | ||
| 116 | } | ||
| 117 | if (reachB) { | ||
| 118 | const mktB = runJson(`curl -s --connect-timeout 5 --max-time 10 ${API_B}/market`); | ||
| 119 | if (mktB?.entries) boards.push({ name: 'B', data: mktB }); | ||
| 120 | } | ||
| 121 | |||
| 122 | for (const { name, data } of boards) { | ||
| 123 | for (const e of data.entries) { | ||
| 124 | assert(typeof e.price_per_step === 'number' && e.price_per_step > 0, | ||
| 125 | `Board ${name} entry has valid price (${e.price_per_step})`); | ||
| 126 | assert(typeof e.step_size === 'number' && e.step_size > 0, | ||
| 127 | `Board ${name} entry has valid step_size (${e.step_size})`); | ||
| 128 | assert(typeof e.metric === 'string' && e.metric.length > 0, | ||
| 129 | `Board ${name} entry has valid metric (${e.metric})`); | ||
| 130 | assert(typeof e.rssi === 'number', | ||
| 131 | `Board ${name} entry has valid RSSI (${e.rssi})`); | ||
| 132 | break; | ||
| 133 | } | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | console.log(`\n=== Results: ${passed} passed, ${failed} failed ===`); | ||
| 138 | process.exit(failed > 0 ? 1 : 0); | ||