Files
MikrotikManager/backend/scripts/acceptance/chr-rest-diagnostic.mjs
T

52 lines
3.6 KiB
JavaScript

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 }))