Files
MikrotikManager/backend/src/services/traffic-observation.test.ts
T

69 lines
5.2 KiB
TypeScript

import {parseTrafficPeers} from "./traffic-peer-observation.js"
import assert from "node:assert/strict"
import { observedTrafficRate, parseTrafficCounter, parseTrafficFlag, nativeTrafficCounterIdentity } from "./traffic-observation.js"
import { collectSourceSignals } from "./alert-engine/collector-source-signals.js"
import fs from "node:fs"
import vm from "node:vm"
import ts from "typescript"
import { bpsToMbps, shouldIncludeIface } from "./traffic-rate.js"
const now = Date.parse("2026-10-12T12:00:00Z"), iso = new Date(now).toISOString()
const previous = { rxBytes: 1000, txBytes: 1000, sampledAt: new Date(now - 30_000).toISOString() }
for (const value of [undefined, null, "", "123bad", "1.5", "-1", -1, NaN, Infinity, Number.MAX_SAFE_INTEGER + 1, {}, " 10 "]) {
assert.throws(() => parseTrafficCounter(value), /некорректный счётчик/)
}
for (const value of [0, "0", "1000", Number.MAX_SAFE_INTEGER]) assert.equal(parseTrafficCounter(value), Number(value))
const cases = [
observedTrafficRate(undefined, 1000, 1000, now),
observedTrafficRate(previous, 10, 10, now),
observedTrafficRate(previous, 10, 1100, now),
observedTrafficRate({ ...previous, sampledAt: iso }, 1100, 1100, now),
observedTrafficRate({ ...previous, sampledAt: "invalid" }, 1100, 1100, now),
observedTrafficRate(previous, 1000, 1000, now),
observedTrafficRate(previous, 3_751_000, 3_751_000, now),
]
assert.deepEqual(cases.map(r => r.measured), [false, false, false, false, false, true, true])
assert.equal(cases[5].rxBps, 0); assert.equal(cases[6].rxBps, 1_000_000)
for (const rate of cases) {
const row = { serverId: 1, name: "audit", host: "audit.example", ok: true,
...(rate.measured ? { sumRxMbps: rate.rxBps / 1_000_000, sumTxMbps: rate.txBps / 1_000_000 } : {}) }
const signals = collectSourceSignals({ servers: [{ id: 1, name: "audit", host: "audit.example", enabled: true }], probes: [], traffic: { enabled: true, intervalSec: 30 } },
{ resources: [], rest: [], ping: [], traffic: [{ finishedAt: iso, resultJson: { v: 1, job: "traffic", sampledAt: iso, servers: [row] } }] }, now)
assert.equal(signals.trafficByServer.length, Number(rate.measured))
}
console.log("traffic observation counters/rates/source confidence tests ok")
// Execute the actual collector body with in-memory DB/HTTP boundaries, not a copied sum algorithm.
const collector = fs.readFileSync(new URL("./traffic-collector.ts", import.meta.url), "utf8")
const ast = ts.createSourceFile("traffic-collector.ts", collector, ts.ScriptTarget.Latest, true)
const functions = ast.statements.filter(n => ts.isFunctionDeclaration(n) && n.name &&
["waveKey", "sampleRate", "toNum", "collectTrafficOnce"].includes(n.name.text)).map(n => n.getText(ast)).join("\n")
assert.equal(ast.statements.filter(n => ts.isFunctionDeclaration(n) && n.name && n.name.text === "collectTrafficOnce").length, 1)
let inserted: unknown[] = [], wave = new Map<string, typeof previous & {nativeCounterIdentity?:string|null}>(), counters: unknown = "1000"
let included = true
const context = vm.createContext({
Date, nativeTrafficCounterIdentity,parseTrafficPeers, observedTrafficRate, parseTrafficCounter, parseTrafficFlag, bpsToMbps, shouldIncludeIface,
SCHEDULER_RUN_SNAPSHOT_VERSION: 1, servers: {}, trafficSamples: {}, trafficSettings: {}, eq: () => ({}),
getSettingsRow: async () => ({}), readPreviousWave: async () => wave, rememberServerIfaces: () => {},
encodeTrafficFlags: () => 1,
recordTrafficCollectionAttempt: async () => {},
MikrotikClient: { fromServer: () => ({ get: async (path: string) => path === "/interface" ?
[{ ".id":"*1",name: "ether1", running: String(included), disabled: "false", "rx-byte": counters, "tx-byte": counters }] : [] }) },
db: {
select: () => ({ from: () => ({ where: () => {const rows=[{updatedAt:iso,id:1,name:"audit",host:"audit.example",enabled:true}];return Object.assign(Promise.resolve(rows),{limit:async()=>rows})} }) }),
insert: () => ({ values: async (rows: unknown[]) => { inserted.push(...rows) } }),
update: () => ({ set: () => ({ where: async () => {} }) }),
},
})
vm.runInContext(ts.transpileModule("let collecting=false;\n" + functions.replace("export async function", "async function") +
"\nglobalThis.run=collectTrafficOnce;", { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText, context)
let snapshot = await context.run()
assert.equal(snapshot.servers[0].ok, true); assert.equal(snapshot.servers[0].sumRxMbps, undefined); assert.equal(inserted.length, 1)
wave = new Map([["ether1\0", { ...previous, nativeCounterIdentity:nativeTrafficCounterIdentity(iso,"*1"),sampledAt: new Date(Date.now() - 30_000).toISOString() }]])
snapshot = await context.run(); assert.equal(snapshot.servers[0].sumRxMbps, 0)
counters = "10"; snapshot = await context.run(); assert.equal(snapshot.servers[0].sumRxMbps, undefined)
counters = "bad"; inserted = []; snapshot = await context.run()
assert.equal(snapshot.servers[0].ok, false); assert.equal(inserted.length, 0); assert.match(snapshot.servers[0].error, /некорректный счётчик/)
counters = "1000"; included = false; snapshot = await context.run(); assert.equal(snapshot.servers[0].sumRxMbps, undefined)
console.log("actual traffic collector first-wave/zero/reset/invalid/excluded totals tests ok")