Files
meshcore-analyzer/tests/unit/test-issue-1849-trace-hashbytes.js
T
Alex B 893773338e chore(tests): move root test-*.js into tests/unit and tests/e2e (#2036)
Moves 290 root test-*.js into tests/unit (177, listed in test-all.sh) and tests/e2e (113, classified in scripts/non-unit-tests.json), per #1981 and PR-D of #1385. Root goes from 348 entries to 48. test-all.sh and test-fixtures/ stay put. The inventory guard now fails if a test reappears in the root or sits in the wrong folder.

Verified independently of the diff: the invoked sets are unchanged (test-all.sh 177 before and after, deploy.yml 96 before and after, both identical as sets), and a full local run of test-all.sh on master and on the branch produced 4702 output lines each whose only differences are absolute paths, stack-trace line numbers shifted by the REPO_ROOT line, the inventory wording and two perf ratios. The guard was mutation-checked: a test back in the root, a unit suite in tests/e2e, and a suite dropped from test-all.sh each make it exit 1. CI run 35246304316 ran 97 suites from tests/e2e and is green.

Follow-up 9335c51d finished the instruction files: no bare root test command is left in AGENTS.md, the squad charters, .github or docs, and every tests/ path they name resolves.

Merged by the interim maintainer without a second human reviewer: CI and the local runs above are the independent checks.

Known and deliberately out of scope: 18 of the 113 files in tests/e2e are invoked by no runner at all, and one of them cannot run anywhere because it requires jsdom, which is not a declared dependency. Tracked separately.
2026-09-17 19:06:07 +02:00

152 lines
6.4 KiB
JavaScript

/**
* #1849 — "Hop Bytes" (col-hashsize) column shows misleading `1` for TRACE packets.
*
* TRACE packets (payload_type=9) use header path bytes as per-hop SNR readings, not
* truncated hop hashes (see internal/packetpath/route.go PathBytesAreHops(TRACE)=false).
* The high-2-bit "hash_size" derivation is meaningless for TRACE.
*
* Fix: at the 3 render sites in public/packets.js (buildGroupRowHtml header, its child
* rows, buildFlatRowHtml), when payload_type === 9 render `—` in col-hashsize with an
* explanatory title tooltip. Non-TRACE behavior unchanged.
*/
'use strict';
const vm = require('vm');
const fs = require('fs');
const assert = require('assert');
let passed = 0, failed = 0;
function test(name, fn) {
try { fn(); passed++; console.log(' ✅ ' + name); }
catch (e) { failed++; console.log(' ❌ ' + name + ': ' + e.message); }
}
// Reuse the sandbox construction from test-packets.js (kept in sync intentionally).
function makeSandbox() {
const registeredPages = {};
const ctx = {
window: {
addEventListener: () => {}, removeEventListener: () => {}, dispatchEvent: () => {},
innerWidth: 1200, PacketFilter: null,
},
document: {
readyState: 'complete',
createElement: () => ({ id: '', textContent: '', innerHTML: '', className: '', style: {},
appendChild: () => {}, setAttribute: () => {}, addEventListener: () => {},
querySelectorAll: () => [], querySelector: () => null,
classList: { add: () => {}, remove: () => {}, contains: () => false } }),
head: { appendChild: () => {} }, getElementById: () => null,
addEventListener: () => {}, removeEventListener: () => {},
querySelectorAll: () => [], querySelector: () => null, body: { appendChild: () => {} },
},
console, Date, Infinity, Math, Array, Object, String, Number, JSON, RegExp,
Error, TypeError, RangeError, parseInt, parseFloat, isNaN, isFinite,
encodeURIComponent, decodeURIComponent,
setTimeout: () => {}, clearTimeout: () => {}, setInterval: () => {}, clearInterval: () => {},
fetch: () => Promise.resolve({ ok: true, json: () => Promise.resolve({}) }),
performance: { now: () => Date.now() },
localStorage: (() => { const s = {}; return {
getItem: k => s[k] || null, setItem: (k, v) => { s[k] = String(v); }, removeItem: k => { delete s[k]; },
}; })(),
location: { hash: '' }, history: { replaceState: () => {} },
CustomEvent: class CustomEvent {}, Map, Set, Promise, URLSearchParams,
addEventListener: () => {}, removeEventListener: () => {}, dispatchEvent: () => {},
requestAnimationFrame: (cb) => setTimeout(cb, 0),
registerPage: (name, handler) => { registeredPages[name] = handler; },
};
vm.createContext(ctx);
return ctx;
}
function loadInCtx(ctx, file) {
vm.runInContext(fs.readFileSync(file, 'utf8'), ctx, { filename: file });
for (const k of Object.keys(ctx.window)) { ctx[k] = ctx.window[k]; }
}
function loadPacketsSandbox() {
const ctx = makeSandbox();
loadInCtx(ctx, 'public/payload-labels.js');
loadInCtx(ctx, 'public/roles.js');
loadInCtx(ctx, 'public/app.js');
loadInCtx(ctx, 'public/packet-helpers.js');
vm.runInContext(`
window.HopDisplay = {
renderHop: function(h, entry, opts) { return '<span>' + h + '</span>'; },
_showFromBtn: function() {}
};
`, ctx);
loadInCtx(ctx, 'public/packets.js');
return ctx;
}
console.log('\n=== #1849: TRACE col-hashsize renders — not misleading integer ===');
{
const ctx = loadPacketsSandbox();
const api = ctx._packetsTestAPI;
assert(api, '_packetsTestAPI must be exposed');
test('buildFlatRowHtml: TRACE (payload_type=9) renders — in col-hashsize with title tooltip', () => {
const p = {
id: 100, hash: 'trace1', timestamp: '', observer_id: null,
raw_hex: '00090000000000000000000102030405', payload_type: 9,
route_type: 0, decoded_json: '{}', path_json: '[]'
};
const html = api.buildFlatRowHtml(p);
const m = html.match(/<td class="col-hashsize[^"]*"([^>]*)>([^<]*)<\/td>/);
assert(m, 'col-hashsize cell must be present. Got: ' + html.slice(0, 500));
assert.strictEqual(m[2].trim(), '—',
'TRACE col-hashsize must be — (em-dash), got: ' + JSON.stringify(m[2]));
assert(/title="[^"]*TRACE[^"]*"/.test(m[1]),
'col-hashsize <td> must carry a title attribute mentioning TRACE. Got attrs: ' + m[1]);
});
test('buildFlatRowHtml: non-TRACE (payload_type=0) still renders numeric hashBytes', () => {
const p = {
id: 101, hash: 'adv1', timestamp: '', observer_id: null,
raw_hex: '00000000000000000000C100', payload_type: 0,
route_type: 0, decoded_json: '{}', path_json: '[]'
};
const html = api.buildFlatRowHtml(p);
const m = html.match(/<td class="col-hashsize[^"]*"[^>]*>([^<]*)<\/td>/);
assert(m, 'col-hashsize cell must be present');
assert(/^\d+$/.test(m[1].trim()),
'non-TRACE col-hashsize must be numeric, got: ' + JSON.stringify(m[1]));
});
test('buildGroupRowHtml: TRACE header row (payload_type=9) renders — in col-hashsize with title', () => {
const p = {
hash: 'trg1', count: 1, latest: '',
observer_id: null, raw_hex: '00090000000000000000000102030405',
payload_type: 9, route_type: 0, decoded_json: '{}', path_json: '[]',
observation_count: 1, observer_count: 1,
};
const html = api.buildGroupRowHtml(p);
const m = html.match(/<td class="col-hashsize[^"]*"([^>]*)>([^<]*)<\/td>/);
assert(m, 'col-hashsize cell must be present');
assert.strictEqual(m[2].trim(), '—',
'TRACE group header col-hashsize must be —, got: ' + JSON.stringify(m[2]));
assert(/title="[^"]*TRACE[^"]*"/.test(m[1]),
'col-hashsize <td> must carry title mentioning TRACE');
});
test('buildGroupRowHtml: non-TRACE header row still numeric', () => {
const p = {
hash: 'grp1', count: 1, latest: '',
observer_id: null, raw_hex: '00000000000000000000C100',
payload_type: 0, route_type: 0, decoded_json: '{}', path_json: '[]',
observation_count: 1, observer_count: 1,
};
const html = api.buildGroupRowHtml(p);
const m = html.match(/<td class="col-hashsize[^"]*"[^>]*>([^<]*)<\/td>/);
assert(m, 'col-hashsize cell must be present');
assert(/^\d+$/.test(m[1].trim()),
'non-TRACE group col-hashsize must be numeric, got: ' + JSON.stringify(m[1]));
});
}
console.log('');
if (failed > 0) {
console.error(`❌ ${failed} test(s) failed, ${passed} passed`);
process.exit(1);
}
console.log(`✅ All ${passed} tests passed`);