45 lines
5.7 KiB
TypeScript
45 lines
5.7 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import Fastify from "fastify"
|
|
import { SignJWT } from "jose"
|
|
import { speedTestCommandSchema, type SpeedTestCommand } from "@mmapp/contracts"
|
|
import { createSpeedTestRoute } from "./speed-test.js"
|
|
import { SpeedProbeEndpointsChanged } from "../services/speed-run-persistence.js"
|
|
const commands: SpeedTestCommand[] = [], failures: Array<SpeedTestCommand & { message: string }> = []
|
|
let lookups = 0, missing = false, problem: Error | null = null
|
|
const result = (c: SpeedTestCommand) => ({ runId: c.runId ?? "ad-hoc", srcInterface: c.srcInterface || null, dstInterface: c.dstInterface || null, address: "192.0.2.2", srcAddress: "192.0.2.1", dstAddress: "192.0.2.2", srcInterfaceAddress: null, dstInterfaceAddress: null, protocol: c.protocol, direction: c.direction, durationSec: c.durationSec, txAvgMbps: null, rxAvgMbps: 0, txSeries: [], rxSeries: [0], raw: [{ status: "done testing" }], afterBtPing: { rttMs: null, lossPct: null, error: null } })
|
|
const deps = {
|
|
serverExists: async (_id: number) => { lookups++; return !missing },
|
|
run: async (c: SpeedTestCommand) => { commands.push(c); if (problem) throw problem; return result(c) },
|
|
failure: async (c: SpeedTestCommand & { message: string }) => { failures.push(c) },
|
|
}
|
|
const app = Fastify(); await app.register(createSpeedTestRoute(deps), { prefix: "/api" })
|
|
const send = (body: unknown) => app.inject({ method: "POST", url: "/api/uptime/speed-test", headers: { "content-type": "application/json" }, payload: JSON.stringify(body) })
|
|
const base = { srcServerId: 1, dstServerId: 2 }
|
|
const bad = [null, [], {}, { ...base, srcServerId: "1junk" }, { ...base, srcServerId: "1.0" }, { ...base, srcServerId: "01" }, { ...base, srcServerId: true }, { ...base, srcServerId: 0 }, { ...base, srcServerId: -1 }, { ...base, srcServerId: 1.5 }, { ...base, srcServerId: 9007199254740992 }, { ...base, dstServerId: "1" }, { ...base, protocol: "TCP" }, { ...base, protocol: null }, { ...base, direction: "other" }, { ...base, durationSec: "10junk" }, { ...base, durationSec: "3.5" }, { ...base, durationSec: 2 }, { ...base, durationSec: 2147483648 }, { ...base, srcInterface: null }, { ...base, runId: 123 }, { ...base, probeId: false }, ...["runId", "probeId", "srcInterface", "dstInterface"].map(key => ({ ...base, [key]: "bad" + String.fromCharCode(0) }))]
|
|
for (const body of bad) assert.equal((await send(body)).statusCode, 400, JSON.stringify(body))
|
|
assert.equal(lookups, 0); assert.equal(commands.length, 0); assert.equal(failures.length, 0)
|
|
assert.equal(speedTestCommandSchema.safeParse({ ...base, durationSec: Infinity }).success, false)
|
|
const first = await send(base); assert.equal(first.statusCode, 200); assert.equal(first.json().result.txAvgMbps, null); assert.equal(first.json().result.rxAvgMbps, 0)
|
|
assert.deepEqual(commands[0], { ...base, srcInterface: "", dstInterface: "", protocol: "tcp", direction: "both", durationSec: 10 })
|
|
const explicit = await send({ srcServerId: "1", dstServerId: "2", runId: " run-2 ", probeId: " probe ", srcInterface: " ether1 ", protocol: "udp", direction: "receive", durationSec: "3", lastTxAvgMbps: 999 })
|
|
assert.equal(explicit.statusCode, 200); assert.equal(commands[1].runId, "run-2"); assert.equal(commands[1].probeId, "probe"); assert.equal(commands[1].srcInterface, "ether1"); assert.equal(commands[1].protocol, "udp"); assert.equal(commands[1].direction, "receive"); assert.equal(commands[1].durationSec, 3); assert.equal("lastTxAvgMbps" in commands[1], false)
|
|
assert.equal(speedTestCommandSchema.parse({ ...base, runId: " ", probeId: "" }).probeId, undefined)
|
|
missing = true; assert.equal((await send(base)).statusCode, 404); assert.equal(commands.length, 2); assert.equal(failures.length, 0); missing = false
|
|
for (const [error, code] of [[new SpeedProbeEndpointsChanged(), 409], [Error("На интерфейсе назначения нет активного IP"), 400], [Error("Server not found"), 400], [Error("authentication failed"), 502], [Error("socket hang up"), 502]] as const) {
|
|
problem = error; const response = await send({ ...base, runId: "failed", direction: "transmit", durationSec: 20 }); assert.equal(response.statusCode, code); assert.equal(failures.at(-1)?.runId, "failed"); assert.equal(failures.at(-1)?.durationSec, 20); assert.equal(failures.at(-1)?.direction, "transmit")
|
|
}
|
|
await app.close(); console.log("Speed-test HTTP:26 invalid inputs before lookups/native/history; defaults/ad-hoc/trim/nullable/zero/404/409/400/502")
|
|
process.env.AUTH_REQUIRED = "true"; process.env.AUTH_JWT_SECRET = "speed-command-test-secret"; process.env.AUTH_ISSUER = "https://auth.test.local"; process.env.NODE_ENV = "test"
|
|
const { default: authPlugin, requireAuth } = await import("../plugins/auth.js")
|
|
const secured = Fastify(); await secured.register(authPlugin); secured.addHook("preHandler", requireAuth)
|
|
problem = null; const before = { lookups, runs: commands.length }
|
|
await secured.register(createSpeedTestRoute(deps), { prefix: "/api" })
|
|
const authorized = (token?: string) => secured.inject({ method: "POST", url: "/api/uptime/speed-test", payload: base, headers: token ? { authorization: "Bearer " + token } : {} })
|
|
assert.equal((await authorized()).statusCode, 401)
|
|
for (const permission of ["mm:uptime:read", "mm:servers:write", "mm:uptime:write"]) {
|
|
const token = await new SignJWT({ sub: "fixture", apps: ["mm"], permissions: [permission] }).setProtectedHeader({ alg: "HS256" }).setIssuer("https://auth.test.local").setExpirationTime("1h").sign(new TextEncoder().encode("speed-command-test-secret"))
|
|
assert.equal((await authorized(token)).statusCode, permission === "mm:uptime:write" ? 200 : 403)
|
|
}
|
|
assert.equal(lookups, before.lookups + 2); assert.equal(commands.length, before.runs + 1)
|
|
await secured.close(); console.log("Speed-test RBAC:401/403 before dependencies, uptime writer accepted; no PostgreSQL or RouterOS")
|