mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-15 07:50:04 +00:00
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.
112 lines
5.0 KiB
JavaScript
112 lines
5.0 KiB
JavaScript
/**
|
|
* Issue #1473 — Prefix generator (#/analytics?tab=prefix-tool) must NOT
|
|
* suggest any prefix whose FIRST byte is 0x00 or 0xFF, since the MeshCore
|
|
* firmware keygen routine re-rolls such identities by convention (HEAD
|
|
* 8ede7641, examples/simple_repeater/main.cpp:83).
|
|
*
|
|
* Reporter: @halo779 (community).
|
|
*/
|
|
'use strict';
|
|
const REPO_ROOT = require('path').resolve(__dirname, '..', '..');
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const vm = require('vm');
|
|
const assert = require('assert');
|
|
|
|
const src = fs.readFileSync(path.join(REPO_ROOT, 'public', 'prefix-reserved.js'), 'utf8');
|
|
const sandbox = { module: { exports: {} }, exports: {}, window: {} };
|
|
vm.createContext(sandbox);
|
|
vm.runInContext(src, sandbox);
|
|
const PR = (sandbox.module.exports && sandbox.module.exports.isReservedPrefix)
|
|
? sandbox.module.exports
|
|
: sandbox.window.PrefixReserved;
|
|
|
|
let passed = 0, failed = 0;
|
|
function test(name, fn) {
|
|
try { fn(); passed++; console.log(' \u2713 ' + name); }
|
|
catch (e) { failed++; console.error(' \u2717 ' + name + ': ' + e.message); }
|
|
}
|
|
|
|
console.log('\n=== #1473: filterReserved() drops 00 and FF from 1-byte space ===');
|
|
const all1byte = [];
|
|
for (let i = 0; i < 256; i++) all1byte.push(i.toString(16).toUpperCase().padStart(2, '0'));
|
|
const filtered = PR.filterReserved(all1byte);
|
|
|
|
test('filtered length is 254 (256 - 2 reserved)', () => assert.strictEqual(filtered.length, 254));
|
|
test('"00" not in filtered output', () => assert.strictEqual(filtered.indexOf('00'), -1));
|
|
test('"FF" not in filtered output', () => assert.strictEqual(filtered.indexOf('FF'), -1));
|
|
test('"01" still present', () => assert.ok(filtered.indexOf('01') >= 0));
|
|
test('"FE" still present', () => assert.ok(filtered.indexOf('FE') >= 0));
|
|
test('"A3" still present', () => assert.ok(filtered.indexOf('A3') >= 0));
|
|
|
|
console.log('\n=== #1473: reservedCount() per byte length ===');
|
|
test('1-byte reserved count = 2', () => assert.strictEqual(PR.reservedCount(1), 2));
|
|
test('2-byte reserved count = 512', () => assert.strictEqual(PR.reservedCount(2), 512));
|
|
test('3-byte reserved count = 131072', () => assert.strictEqual(PR.reservedCount(3), 131072));
|
|
|
|
console.log('\n=== #1473: simulated generator never returns a reserved prefix ===');
|
|
// Mirrors the production generator loop: random sampling + reserved filter.
|
|
// We pre-seed the RNG to return the reserved hex values up front; the loop
|
|
// MUST iterate past them and end on a non-reserved value.
|
|
function generateOne(bytes, usedSet, rng) {
|
|
const totalSpace = Math.pow(256, bytes);
|
|
const hexLen = bytes * 2;
|
|
let attempts = 0, prefix;
|
|
do {
|
|
prefix = Math.floor(rng() * totalSpace).toString(16).toUpperCase().padStart(hexLen, '0');
|
|
} while ((usedSet.has(prefix) || PR.isReservedPrefix(prefix)) && ++attempts < 2000);
|
|
return prefix;
|
|
}
|
|
|
|
// Sequence that biases towards reserved first, then a valid prefix.
|
|
const seq = [0, 255, 0, 255, 0, 0x42, 0x77, 0x10];
|
|
let rngIdx = 0;
|
|
const biasedRng = () => seq[rngIdx++ % seq.length] / 256;
|
|
for (let k = 0; k < 20; k++) {
|
|
const out = generateOne(1, new Set(), biasedRng);
|
|
test('1-byte gen run #' + k + ' (' + out + ') is not reserved',
|
|
() => assert.strictEqual(PR.isReservedPrefix(out), false));
|
|
}
|
|
|
|
// Enumeration-style generator (fallback path in production) — first available
|
|
// non-reserved.
|
|
function enumerateFirstFree(bytes, usedSet) {
|
|
const totalSpace = Math.pow(256, bytes);
|
|
const hexLen = bytes * 2;
|
|
for (let i = 0; i < totalSpace; i++) {
|
|
const p = i.toString(16).toUpperCase().padStart(hexLen, '0');
|
|
if (!usedSet.has(p) && !PR.isReservedPrefix(p)) return p;
|
|
}
|
|
return null;
|
|
}
|
|
test('enumerate-first-free (1-byte, empty used) returns "01" (skips 00)',
|
|
() => assert.strictEqual(enumerateFirstFree(1, new Set()), '01'));
|
|
|
|
// Used = everything except 00, FE, FF → must return FE, NOT 00 or FF.
|
|
const usedAllBut3 = new Set();
|
|
for (let i = 0; i < 256; i++) {
|
|
if (i !== 0x00 && i !== 0xFE && i !== 0xFF) {
|
|
usedAllBut3.add(i.toString(16).toUpperCase().padStart(2, '0'));
|
|
}
|
|
}
|
|
test('with only {00, FE, FF} free, generator returns "FE" (NOT 00 or FF)',
|
|
() => assert.strictEqual(enumerateFirstFree(1, usedAllBut3), 'FE'));
|
|
|
|
console.log('\n=== #1473: analytics.js wires reserved filter into the generator ===');
|
|
const analyticsSrc = fs.readFileSync(path.join(REPO_ROOT, 'public', 'analytics.js'), 'utf8');
|
|
test('analytics.js references PrefixReserved (generator wiring)',
|
|
() => assert.ok(/PrefixReserved/.test(analyticsSrc)));
|
|
test('analytics.js mentions the reserved-excluded note in the generator card',
|
|
() => assert.ok(/0x00 and 0xFF[\s\S]{0,200}excluded/i.test(analyticsSrc)));
|
|
test('analytics.js loads prefix-reserved before analytics in index.html',
|
|
() => {
|
|
const html = fs.readFileSync(path.join(REPO_ROOT, 'public', 'index.html'), 'utf8');
|
|
const pri = html.indexOf('prefix-reserved.js');
|
|
const ani = html.indexOf('analytics.js');
|
|
assert.ok(pri > 0 && ani > 0 && pri < ani, 'prefix-reserved.js must load before analytics.js');
|
|
});
|
|
|
|
console.log('\n' + passed + ' passed, ' + failed + ' failed');
|
|
if (failed) process.exit(1);
|