// node --test relay/test/parse.test.mjs relay/test/auth.test.mjs (no dependencies; CI runs both in the site job) import { test } from 'node:test'; import assert from 'node:assert/strict'; import { parseLabel, parseMinerTail, markStale, STALE_S } from '../lib/parse.mjs'; test('labels: every vendor the app names, the app log, the node log, the legacy launchers', () => { assert.deepEqual(parseLabel('miner-nvidia-ae432dc7-1'), { id: 'ae432dc7', platform: 'win', stream: 'miner', vendor: 'nvidia', card: 1 }); assert.deepEqual(parseLabel('miner-other-37ba0461-1'), { id: '37ba0461', platform: 'win', stream: 'miner', vendor: 'other', card: 1 }); assert.deepEqual(parseLabel('miner-intel-37ba0461-2'), { id: '37ba0461', platform: 'win', stream: 'miner', vendor: 'intel', card: 2 }); assert.deepEqual(parseLabel('miner-mac-d937c69d-1'), { id: 'd937c69d', platform: 'mac', stream: 'miner', vendor: 'mac', card: 1 }); assert.deepEqual(parseLabel('nodelog-win-1ccfe586'), { id: '1ccfe586', platform: 'win', stream: 'node' }); assert.deepEqual(parseLabel('win-1ccfe586'), { id: '1ccfe586', platform: 'win', stream: 'app' }); assert.equal(parseLabel('nodelog-DESKTOP-KMCV30N').platform, 'legacy'); assert.equal(parseLabel('job-run'), null); }); test('miner tail: the last STATUS line gives the numbers, ACCEPTED lines are counted without one', () => { const tail = [ "1791140549.352 STATUS 'other-37ba0461-1' [worker]: 30s jobs=15 accepted=1 rejected=0 mismatched=0 extra=0 rate=0.03 blocks/s hash=1.04 MH/s wall (1.57 MH/s inside jobs) now=1.04 MH/s wall", '1791140553.360 ACCEPTED block nonce=0xa27b8e5b000a82f6 (gpu worker, cpu re-check ok, identity other-37ba0461-1)', "1791140579.479 STATUS 'other-37ba0461-1' [worker]: 61s jobs=37 accepted=2 rejected=0 mismatched=0 extra=0 rate=0.02 blocks/s hash=1.28 MH/s wall (1.55 MH/s inside jobs) now=1.53 MH/s wall", ].join('\n'); const c = parseMinerTail(tail); assert.equal(c.accepted, 2); assert.equal(c.hash_now, 1.53); assert.equal(c.hash_avg, 1.28); assert.equal(c.uptime_s, 61); assert.equal(c.status_at, new Date(1791140579479).toISOString()); assert.equal(c.faults, 0); const noStatus = parseMinerTail('1 ACCEPTED block x\n2 ACCEPTED block y\n3 WORKER MISMATCH z'); assert.equal(noStatus.accepted, 2); assert.equal(noStatus.status_at, null); assert.equal(noStatus.faults, 1); }); test('stale: a card is "now" within STALE_S of its STATUS line and stale after it or without one', () => { const now = Date.parse('2026-10-04T19:03:00Z'); const fresh = { status_at: new Date(now - 58_000).toISOString(), hash_now: 18.9 }; const old = { status_at: new Date(now - 4 * 60_000).toISOString(), hash_now: 117.2 }; const none = { status_at: null, hash_now: 0 }; markStale([fresh, old, none], now); assert.equal(fresh.stale, false); assert.equal(fresh.status_age_s, 58); assert.equal(old.stale, true); assert.equal(old.status_age_s, 240); assert.equal(none.stale, true); assert.equal(none.status_age_s, null); assert.ok(STALE_S >= 160, 'one missed 60 s upload plus a 30 s STATUS age plus the 10 s cache is not stale'); const oneMissed = { status_at: new Date(now - 150_000).toISOString(), hash_now: 18 }; markStale([oneMissed], now); assert.equal(oneMissed.stale, false); }); test('app tail: a clean quit after the last status line is "stopped (quit)", an update is "stopped (update)", a running app is not stopped', async () => { const { parseAppTail } = await import('../lib/parse.mjs'); const running = ['1791126700 status: accepted 10 blocks (5 this run), 18.00 MH/s, mining | node 100 blocks, 4 peers, synced | up 00:10:00'].join('\n'); assert.equal(parseAppTail(running).stopped, undefined); const quit = [ '1791126700 status: accepted 10 blocks (5 this run), 18.00 MH/s, mining | node 100 blocks, 4 peers, synced | up 00:10:00', '1791126735 quit: stopping the miners, then the node', '1791126735 stopping the miners (quit)', '1791126736 node stopped (exit Some(0))', '1791126736 SUMMARY after 00:26:53: node started 1 time(s), restarts 0, 1165 accepted blocks this run (1270 lifetime); data stays in x', '1791126737 stopped', ].join('\n'); const q = parseAppTail(quit); assert.deepEqual([q.stopped.reason, q.stopped.at], ['quit', new Date(1791126737000).toISOString()]); const update = [ '1791125100 status: accepted 1 blocks (1 this run), 1.00 MH/s, mining | node 1 blocks, 1 peers, synced | up 00:01:00', '1791125117 [info] installing Igneum Miner 0.3.2: the miners stop, then the node, then the app opens again', '1791125118 quit: stopping the miners, then the node', '1791125121 stopped', ].join('\n'); assert.equal(parseAppTail(update).stopped.reason, 'update'); // a quit line from an earlier run in the same tail, with a status line after it: still running const resumed = quit + '\n1791126800 status: accepted 11 blocks (1 this run), 18.00 MH/s, mining | node 120 blocks, 4 peers, synced | up 00:01:00'; assert.equal(parseAppTail(resumed).stopped, undefined); });