Files
MikrotikManager/backend/src/test/traffic-flow-chr-ingress-discovery.test.ts
T

80 lines
4.3 KiB
TypeScript

import assert from "node:assert/strict"
import { test } from "node:test"
import { discoverChrIngressEndpoint } from "./traffic-flow-chr-ingress-discovery.js"
type Options = Parameters<typeof discoverChrIngressEndpoint>[0]
function fixture() {
const reads: string[] = [], toolReads: string[] = []
const collectors = { enabled: true, collectorIp: "192.0.2.100", flowListenPort: 4740,
listenerBound: true, listenerAddress: "192.0.2.100:4740" }
let revoked = false
const mappings = new Map([["192.0.2.10", 1], ["192.0.2.20", 2]])
const native = (serverId: number) => {
const identity = "owned-" + serverId, version = "7.24.5 (stable)", source = "192.0.2." + serverId * 10
const table: Record<string, unknown> = {
"/system/identity": [{ name: identity }],
"/system/resource": { version },
"/ip/address": [{ address: source + "/32", disabled: "false" }],
"/ip/traffic-flow": [{ enabled: "true" }],
"/ip/traffic-flow/target": [{ ".id": "*A", disabled: "false", "dst-address": "192.0.2.100",
"src-address": serverId === 1 ? source : "0.0.0.0", port: "4740", version: "ipfix" }],
}
const device: Options["devices"][0] = {
identity, version, source, serverId,
assertPinnedTransport: async () => { if (revoked) throw Error("transport pin lost") },
client: { async get<T>(path: string, timeout?: number, _signal?: AbortSignal, max?: number, before?: () => Promise<unknown>) {
assert(timeout! > 0 && timeout! <= 5000)
assert.equal(max, 65536)
assert(before)
await before()
reads.push(serverId + ":" + path)
assert(Object.hasOwn(table, path))
return structuredClone(table[path]) as T
} },
}
return { table, device }
}
const a = native(1), b = native(2)
const options: Options = { destination: collectors.collectorIp, baselinePort: 4740, testPort: 4739,
deadlineAt: Date.now() + 5000, devices: [a.device, b.device], assertCurrent: async () => {},
callTool: async (name, input) => { assert.deepEqual(input, {}); toolReads.push(name); return collectors },
resolveExporter: async address => mappings.get(address) ?? null,
}
return { options, reads, toolReads, a, b, mappings, collectors, revoke: () => { revoked = true } }
}
test("discovers exact configured two-CHR baseline endpoint without claiming observed ingress", async () => {
const f = fixture()
const result = await discoverChrIngressEndpoint(f.options)
assert.deepEqual(result.endpoint, { sources: ["192.0.2.10", "192.0.2.20"], destination: "192.0.2.100", port: 4740 })
assert.deepEqual(result.observations.map(row => row.sourceConfiguredExplicitly), [true, false])
assert.equal(result.ingressVerified, false)
assert.equal(f.reads.length, 10)
assert.deepEqual(f.toolReads, ["mm_netflow_settings_get", "mm_netflow_settings_get"])
})
test("refuses absent exact target, competing test port, source drift and attribution mismatch", async () => {
for (const [mutate, reason] of [
[(f: ReturnType<typeof fixture>) => { f.b.table["/ip/traffic-flow/target"] = [] }, /baseline IPFIX target/],
[(f: ReturnType<typeof fixture>) => { (f.a.table["/ip/traffic-flow/target"] as object[]).push({ ".id": "*B", "dst-address": "192.0.2.100", port: "4739" }) }, /test endpoint/],
[(f: ReturnType<typeof fixture>) => { f.b.table["/ip/address"] = [{ address: "192.0.2.99/32", disabled: false }] }, /source is not assigned/],
[(f: ReturnType<typeof fixture>) => { f.mappings.set("192.0.2.20", 1) }, /attribution differs/],
] as const) {
const f = fixture()
mutate(f)
await assert.rejects(discoverChrIngressEndpoint(f.options), reason)
}
})
test("stops on wrong collector bind, lost transport pin and changed CHR identity", async () => {
const wrong = fixture(); wrong.collectors.listenerAddress = "0.0.0.0:4740"
await assert.rejects(discoverChrIngressEndpoint(wrong.options), /collector endpoint/)
assert.deepEqual(wrong.reads, [])
const revoked = fixture(); revoked.revoke()
await assert.rejects(discoverChrIngressEndpoint(revoked.options), /transport pin lost/)
assert.deepEqual(revoked.reads, [])
const changed = fixture(); changed.a.table["/system/identity"] = [{ name: "foreign" }]
await assert.rejects(discoverChrIngressEndpoint(changed.options), /identity or version/)
assert.deepEqual(changed.reads, ["1:/system/identity", "1:/system/resource"])
})