mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-09 04:37:40 +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.
134 lines
6.1 KiB
JavaScript
134 lines
6.1 KiB
JavaScript
// Issue #1638: getConfidenceIndicator should weight per-hash-mode counts so
|
|
// that 6-byte sightings (effectively unambiguous) rank higher than 1-byte
|
|
// sightings (which collide ~8-way across a typical mesh).
|
|
//
|
|
// Strategy: load public/nodes.js inside a minimal browser-shaped sandbox,
|
|
// extract getConfidenceIndicator from the IIFE-scoped module, and exercise
|
|
// it against synthetic NeighborEntry-shaped inputs.
|
|
|
|
'use strict';
|
|
const REPO_ROOT = require('path').resolve(__dirname, '..', '..');
|
|
const fs = require('fs');
|
|
const vm = require('vm');
|
|
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); }
|
|
}
|
|
|
|
// Extract getConfidenceIndicator from nodes.js. The IIFE wraps it as an
|
|
// inner `function getConfidenceIndicator(entry) { ... }` — pull the body
|
|
// via a balanced-brace scan and re-evaluate it standalone.
|
|
function extractGetConfidenceIndicator() {
|
|
const src = fs.readFileSync(REPO_ROOT + '/public/nodes.js', 'utf8');
|
|
const start = src.indexOf('function getConfidenceIndicator(');
|
|
if (start < 0) throw new Error('getConfidenceIndicator not found in nodes.js');
|
|
// Walk braces to find end.
|
|
let i = src.indexOf('{', start);
|
|
let depth = 0;
|
|
for (; i < src.length; i++) {
|
|
if (src[i] === '{') depth++;
|
|
else if (src[i] === '}') { depth--; if (depth === 0) { i++; break; } }
|
|
}
|
|
const fnSrc = src.slice(start, i);
|
|
const sandbox = {};
|
|
vm.createContext(sandbox);
|
|
vm.runInContext(fnSrc + '\nthis.getConfidenceIndicator = getConfidenceIndicator;', sandbox);
|
|
return sandbox.getConfidenceIndicator;
|
|
}
|
|
|
|
const getConfidenceIndicator = extractGetConfidenceIndicator();
|
|
|
|
// Helper: rank labels low<medium<high so we can compare.
|
|
const rank = { 'LOW': 0, 'MEDIUM': 1, 'HIGH': 2, 'AMBIGUOUS': -1 };
|
|
|
|
console.log('=== getConfidenceIndicator: per-hash-mode weighting (#1638) ===');
|
|
|
|
test('mostly 6-byte sightings rank HIGHER than mostly 1-byte at equal count', () => {
|
|
// 5 sightings, all at 1-byte prefixes: low ambiguity-resistance.
|
|
const noisy = {
|
|
ambiguous: false,
|
|
count: 5,
|
|
score: 0.3, // below the legacy HIGH threshold (0.5)
|
|
counts_by_mode: { 1: 5 },
|
|
};
|
|
// Same count, but all 3-byte prefixes: effectively unambiguous evidence
|
|
// per firmware hash modes (Packet.cpp:13-18, 4 reserved).
|
|
const clean = {
|
|
ambiguous: false,
|
|
count: 5,
|
|
score: 0.3,
|
|
counts_by_mode: { 3: 5 },
|
|
};
|
|
const a = getConfidenceIndicator(noisy);
|
|
const b = getConfidenceIndicator(clean);
|
|
assert.ok(rank[b.label] > rank[a.label],
|
|
'expected 3-byte (' + b.label + ') to outrank 1-byte (' + a.label + ') at equal flat count');
|
|
});
|
|
|
|
test('a small number of 3-byte sightings beats many 1-byte sightings', () => {
|
|
// 20 1-byte observations: still high collision ambiguity.
|
|
const noisy = { ambiguous: false, count: 20, score: 0.4, counts_by_mode: { 1: 20 } };
|
|
// 3 3-byte observations: low flat count but each is unambiguous.
|
|
const clean = { ambiguous: false, count: 3, score: 0.4, counts_by_mode: { 3: 3 } };
|
|
const a = getConfidenceIndicator(noisy);
|
|
const b = getConfidenceIndicator(clean);
|
|
assert.ok(rank[b.label] >= rank[a.label],
|
|
'3-byte (n=3, ' + b.label + ') should be at least as confident as 1-byte (n=20, ' + a.label + ')');
|
|
});
|
|
|
|
test('ambiguous flag still wins over per-mode weighting', () => {
|
|
const e = { ambiguous: true, count: 99, score: 0.99, counts_by_mode: { 3: 99 } };
|
|
const r = getConfidenceIndicator(e);
|
|
assert.strictEqual(r.label, 'AMBIGUOUS');
|
|
});
|
|
|
|
test('back-compat: entries without counts_by_mode still classify', () => {
|
|
// Legacy shape (no counts_by_mode) must not throw and must return a known label.
|
|
const e = { ambiguous: false, count: 5, score: 0.6 };
|
|
const r = getConfidenceIndicator(e);
|
|
assert.ok(['LOW','MEDIUM','HIGH'].includes(r.label),
|
|
'expected a known label, got ' + r.label);
|
|
});
|
|
|
|
test('legacy edge with no counts_by_mode falls back to bucket-0 (unknown) weight', () => {
|
|
// No per-mode breakdown — every count contributes 0.5 (unknown bucket).
|
|
// Score below 0.5 means the legacy heuristic does not promote to HIGH;
|
|
// weighted = 5 * 0.5 = 2.5, also below the weighted-HIGH threshold (3),
|
|
// so we land at MEDIUM. Compare against an all-3-byte entry at same
|
|
// count: weighted = 5 * 1.0 = 5.0 → HIGH. Legacy must rank lower.
|
|
const legacy = { ambiguous: false, count: 5, score: 0.3 };
|
|
const clean = { ambiguous: false, count: 5, score: 0.3, counts_by_mode: { 3: 5 } };
|
|
const a = getConfidenceIndicator(legacy);
|
|
const b = getConfidenceIndicator(clean);
|
|
assert.ok(rank[b.label] > rank[a.label],
|
|
'legacy (' + a.label + ') must rank below 3-byte (' + b.label + ')');
|
|
});
|
|
|
|
test('partial counts_by_mode + Count > sum allocates delta to bucket 0', () => {
|
|
// Anti-tautology test (adv #1): edge with Count=10 and CountsByMode={3:4}
|
|
// has a delta of 6 unaccounted-for sightings (e.g. inherited from the
|
|
// persisted snapshot). Those 6 MUST be counted at bucket-0 weight (0.5),
|
|
// not silently dropped and not promoted to the 3-byte (1.0) weight.
|
|
// weighted = 4*1.0 + 6*0.5 = 4 + 3 = 7 → easily clears HIGH (>=3).
|
|
// If the delta were dropped: weighted = 4 → still HIGH, indistinguishable.
|
|
// So compare against an edge with Count=4, CountsByMode={3:4}: weighted=4.
|
|
// Both end up HIGH; instead, verify count totals via a LOW-threshold case.
|
|
// Use Count=10, CountsByMode={1:1}: delta=9 at bucket-0 → weighted =
|
|
// 1*0.125 + 9*0.5 = 4.625, HIGH. If delta were dropped: weighted=0.125
|
|
// and count=10 (> 1), so label would be MEDIUM (not LOW, count>1).
|
|
// To make the difference visible, use Count=10, CountsByMode={1:1},
|
|
// score=0.2 (below legacy HIGH gate):
|
|
// - with delta: weighted = 4.625 → HIGH
|
|
// - without delta: weighted = 0.125 → MEDIUM
|
|
const withDelta = { ambiguous: false, count: 10, score: 0.2, counts_by_mode: { 1: 1 } };
|
|
const r = getConfidenceIndicator(withDelta);
|
|
assert.strictEqual(r.label, 'HIGH',
|
|
'expected HIGH when 9 of 10 sightings get apportioned to bucket-0 weight; got ' + r.label);
|
|
});
|
|
|
|
console.log('\nResult: ' + passed + ' passed, ' + failed + ' failed');
|
|
if (failed > 0) process.exit(1);
|