import assert from 'node:assert/strict' import fs from 'node:fs' const hosts = (process.env.MMAPP_TEST_CHR_HOSTS ?? '').split(',') assert.deepEqual(hosts, ['194.226.121.150', '104.165.244.7'], 'Explicit owned lab hosts required') const { MMAPP_TEST_CHR_USER: username, MMAPP_TEST_CHR_PASSWORD: password } = process.env assert(username && password, 'Explicit test credentials required') const vantage = process.env.MMAPP_TEST_VANTAGE assert(['workstation', 'debian'].includes(vantage), 'Explicit vantage required') const output = process.env.MMAPP_TEST_REPORT assert(output, 'Explicit report path required') const authorization = 'Basic ' + Buffer.from(username + ':' + password).toString('base64') const report = { block: '12.5.225', date: '2026-10-14', vantage, startedAt: new Date().toISOString(), requests: [], diagnostics: [], configurationWrites: 0, passwordChanges: 0, rootCauseKnown: false } const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) async function read(host, path) { const startedAt = new Date().toISOString(), start = Date.now() try { const response = await fetch(`http://${host}/rest${path}`, { headers: { authorization, Connection: 'close' }, signal: AbortSignal.timeout(8000) }) const body = await response.text() report.requests.push({ host, path, startedAt, durationMs: Date.now() - start, status: response.status, headers: Object.fromEntries(['date', 'server', 'www-authenticate', 'connection'].map(key => [key, response.headers.get(key)])) }) if (!response.ok) return null try { return JSON.parse(body) } catch { return null } } catch (error) { report.requests.push({ host, path, startedAt, durationMs: Date.now() - start, status: null, errorName: error.name }) return null } finally { await sleep(300) } } for (let cycle = 1; cycle <= 3; cycle++) { for (const host of hosts) for (const path of ['/system/resource', '/interface/gre']) await read(host, path) if (cycle < 3) await sleep(5000) } for (const host of hosts) { const clock = await read(host, '/system/clock') const services = await read(host, '/ip/service') const logs = await read(host, '/log') const failures = Array.isArray(logs) ? logs.filter(row => /login failure/i.test(String(row.message))) : null const sources = {} const events = failures?.map(row => { const message = String(row.message), source = message.match(/from ([\d.]+)/)?.[1] ?? 'unparsed' const origin = source === '161.104.46.222' ? 'test-debian' : 'other-or-unidentified' sources[origin] = (sources[origin] ?? 0) + 1 return { time: row.time ?? null, origin, via: message.match(/via (\w+)/)?.[1] ?? null } }) report.diagnostics.push({ host, clock: clock ? { date: clock.date, time: clock.time, gmtOffset: clock['gmt-offset'] } : null, wwwServices: Array.isArray(services) ? services.filter(row => row.name === 'www').map(row => ({ disabled: row.disabled, maxSessions: row['max-sessions'] ?? null, dynamic: row.dynamic ?? null })) : null, retainedAuthFailures: failures?.length ?? null, sources, recentFailures: events?.slice(-20) ?? null, retainedLogIsNotCurrentRate: true }) } report.completedAt = new Date().toISOString() report.summary = hosts.map(host => { const rows = report.requests.filter(row => row.host === host); return { host, requests: rows.length, ok: rows.filter(row => row.status === 200).length, unauthorized: rows.filter(row => row.status === 401).length, serverErrors: rows.filter(row => row.status >= 500).length, transportErrors: rows.filter(row => row.status === null).length } }) fs.writeFileSync(output, JSON.stringify(report, null, 2) + '\n') console.log(JSON.stringify({ vantage, summary: report.summary, configurationWrites: 0 }))