|
| 1 | +/* MAC format smoke test for web/app.js. |
| 2 | + * |
| 3 | + * Verifies that the JS frontend accepts the same MAC formats the Python |
| 4 | + * backend does: |
| 5 | + * - colon / hyphen / Cisco dotted / plain hex / space-separated / mixed case |
| 6 | + * - labelled forms ("MAC Address: ...", "HWaddr ...", ifconfig/ipconfig) |
| 7 | + * - bracketed/wrapped forms |
| 8 | + * - prefix-only inputs (MA-L 6, MA-M 7, MA-S 9 hex chars) |
| 9 | + * - vendor text (e.g. "cisco", "3com", "Apple") still routes to fuzzy |
| 10 | + * |
| 11 | + * Strategy: app.js is a module that touches document at import time, so we |
| 12 | + * lift the pure helpers (normalizeMac, extractMacCandidate, isHexish) out of |
| 13 | + * its source and evaluate them in isolation. This avoids needing a DOM stub. |
| 14 | + * |
| 15 | + * Run: node tests/web/mac_formats.mjs |
| 16 | + */ |
| 17 | +import { readFileSync } from 'fs'; |
| 18 | +import { fileURLToPath } from 'url'; |
| 19 | +import { dirname, resolve } from 'path'; |
| 20 | + |
| 21 | +const __dirname = dirname(fileURLToPath(import.meta.url)); |
| 22 | +const APP_JS = resolve(__dirname, '..', '..', 'web/app.js'); |
| 23 | + |
| 24 | +function assert(cond, msg) { |
| 25 | + if (!cond) { |
| 26 | + console.error('FAIL:', msg); |
| 27 | + process.exit(1); |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +const src = readFileSync(APP_JS, 'utf8'); |
| 32 | + |
| 33 | +// Extract the regex constants + helper functions verbatim and eval them. |
| 34 | +// The block we need starts at SEPARATORS_RE and ends just after isHexish(). |
| 35 | +const startIdx = src.indexOf('const SEPARATORS_RE'); |
| 36 | +const endMarker = '\nfunction longestPrefixLookup'; |
| 37 | +const endIdx = src.indexOf(endMarker); |
| 38 | +assert(startIdx >= 0 && endIdx > startIdx, |
| 39 | + `could not locate helper block in app.js (start=${startIdx}, end=${endIdx})`); |
| 40 | +const block = src.slice(startIdx, endIdx); |
| 41 | + |
| 42 | +// Evaluate inside a Function so we control the exports. |
| 43 | +const factory = new Function(` |
| 44 | + ${block} |
| 45 | + return { normalizeMac, extractMacCandidate, isHexish }; |
| 46 | +`); |
| 47 | +const { normalizeMac, extractMacCandidate, isHexish } = factory(); |
| 48 | + |
| 49 | +// ---- normalizeMac symmetry with the Python normalize_mac ---- |
| 50 | +assert(normalizeMac('00:1A:2B:3C:4D:5E') === '001A2B3C4D5E', 'colon'); |
| 51 | +assert(normalizeMac('00-1a-2b-3c-4d-5e') === '001A2B3C4D5E', 'hyphen lower'); |
| 52 | +assert(normalizeMac('001a.2b3c.4d5e') === '001A2B3C4D5E', 'cisco dotted'); |
| 53 | +assert(normalizeMac('001A2B3C4D5E') === '001A2B3C4D5E', 'plain hex'); |
| 54 | +assert(normalizeMac(' 00 1a 2b 3c 4d 5e ') === '001A2B3C4D5E', 'spaces'); |
| 55 | + |
| 56 | +// ---- extractMacCandidate ---- |
| 57 | +const ex = extractMacCandidate; |
| 58 | +assert(ex('00:1A:2B:3C:4D:5E') === '001A2B3C4D5E', 'extract colon'); |
| 59 | +assert(ex('00-1A-2B-3C-4D-5E') === '001A2B3C4D5E', 'extract hyphen'); |
| 60 | +assert(ex('001a.2b3c.4d5e') === '001A2B3C4D5E', 'extract cisco'); |
| 61 | +assert(ex('001A2B3C4D5E') === '001A2B3C4D5E', 'extract plain'); |
| 62 | +assert(ex('MAC Address: 00:1A:2B:3C:4D:5E') === '001A2B3C4D5E', 'extract labelled'); |
| 63 | +assert(ex('MAC: 00-1A-2B-3C-4D-5E') === '001A2B3C4D5E', 'extract MAC: prefix'); |
| 64 | +assert(ex('Hardware Address 0000.0011.2233') === '000000112233', |
| 65 | + 'extract Hardware Address'); |
| 66 | +assert(ex('HWaddr 00:00:00:11:22:33') === '000000112233', 'extract HWaddr'); |
| 67 | +assert(ex('Physical Address. . . . . : 00-00-00-11-22-33') === '000000112233', |
| 68 | + 'extract ipconfig style'); |
| 69 | +assert(ex('ether 00:00:00:11:22:33 txqueuelen 1000') === '000000112233', |
| 70 | + 'extract ifconfig style'); |
| 71 | +assert(ex('(00:1A:2B:3C:4D:5E)') === '001A2B3C4D5E', 'extract parens'); |
| 72 | +assert(ex('[00-1A-2B-3C-4D-5E]') === '001A2B3C4D5E', 'extract brackets'); |
| 73 | +assert(ex('<00:1A:2B:3C:4D:5E>') === '001A2B3C4D5E', 'extract angle brackets'); |
| 74 | +assert(ex('001A2B') === '001A2B', 'extract MA-L prefix'); |
| 75 | +assert(ex('001A2B3') === '001A2B3', 'extract MA-M prefix (7 hex)'); |
| 76 | +assert(ex('001A2B3C4') === '001A2B3C4', 'extract MA-S prefix (9 hex)'); |
| 77 | + |
| 78 | +// Vendor text — should NOT be treated as a MAC |
| 79 | +assert(ex('') === null, 'empty returns null'); |
| 80 | +assert(ex(' ') === null, 'whitespace returns null'); |
| 81 | +assert(ex('3com') === null, '3com (only 2 hex chars contiguous)'); |
| 82 | +assert(ex('Apple Inc') === null, 'Apple Inc returns null'); |
| 83 | +assert(ex('cisco') === null, 'cisco returns null'); |
| 84 | + |
| 85 | +// Longest run wins |
| 86 | +assert(ex('GigabitEthernet0/1 MAC 00:1A:2B:3C:4D:5E up') === '001A2B3C4D5E', |
| 87 | + 'extract picks longest run'); |
| 88 | + |
| 89 | +// ---- isHexish ---- |
| 90 | +assert(isHexish('00:1A:2B:3C:4D:5E') === true, 'isHexish colon'); |
| 91 | +assert(isHexish('001A2B3C4D5E') === true, 'isHexish plain'); |
| 92 | +assert(isHexish('001A2B') === true, 'isHexish prefix'); |
| 93 | +assert(isHexish('001A2B3') === true, 'isHexish MA-M prefix'); |
| 94 | +assert(isHexish('001A2B3C4') === true, 'isHexish MA-S prefix'); |
| 95 | +assert(isHexish('MAC Address: 00:1A:2B:3C:4D:5E') === true, |
| 96 | + 'isHexish labelled'); |
| 97 | +assert(isHexish('(00-1A-2B-3C-4D-5E)') === true, 'isHexish wrapped'); |
| 98 | +assert(isHexish('Hardware Address 0000.0011.2233') === true, |
| 99 | + 'isHexish ifconfig'); |
| 100 | +// vendor queries — must route to fuzzy |
| 101 | +assert(isHexish('cisco') === false, 'isHexish cisco → false'); |
| 102 | +assert(isHexish('Apple') === false, 'isHexish Apple → false'); |
| 103 | +assert(isHexish('3com') === false, 'isHexish 3com → false'); |
| 104 | +assert(isHexish('intel corporation') === false, |
| 105 | + 'isHexish vendor phrase → false'); |
| 106 | + |
| 107 | +console.log('mac_formats.mjs OK'); |
0 commit comments