Files
MikrotikManager/backend/src/routes/backups-operations.test.ts
T

33 lines
3.6 KiB
TypeScript

import assert from "node:assert/strict"
import Fastify from "fastify"
import { validatorCompiler, serializerCompiler } from "@fastify/type-provider-zod"
import { backupJobResponseSchema } from "@mmapp/contracts/backups"
import { createBackupsRoutes } from "./backups.js"
import { permissionForRequest } from "../lib/permissions.js"
const app = Fastify(); app.setValidatorCompiler(validatorCompiler); app.setSerializerCompiler(serializerCompiler)
let created = 0, restored = 0, eventCalls = 0, restoreFail = false
await app.register(createBackupsRoutes({ listServers: async () => [{ id: 1 }, { id: 2 }], create: async id => { created++; if (id === "2") throw new Error("RouterOS unavailable"); return { id: "backup-1", serverId: 1, serverName: "router", filename: "config.rsc", sizeBytes: 0, createdAt: "2026-10-01T00:00:00Z", kind: "manual", storage: "local" } }, restore: async () => { restored++; if (restoreFail) throw new Error("import failed"); return { filename: "config.rsc", serverName: "router" } }, appendEvent: async () => { eventCalls++; throw new Error("event DB unavailable") } }), { prefix: "/api" })
const post = (payload: unknown) => app.inject({ method: "POST", url: "/api/backups/create", payload: payload as Record<string, unknown> })
for (const serverIds of [[], ["1", "missing"], ["1", "3"], ["1", "1"], ["0"], ["1junk"], [9007199254740992]]) assert.equal((await post({ serverIds })).statusCode, 400)
assert.equal(created, 0)
const ack = await post({ serverIds: [1, "2"] }); assert.equal(ack.statusCode, 202)
const id = ack.json().jobId
for (let attempt = 0; attempt < 20; attempt++) {
const observation = await app.inject({ method: "GET", url: "/api/backups/jobs/" + id })
const job = backupJobResponseSchema.parse(observation.json())
if (job.status === "done") { assert.equal(job.completed, 2); assert.equal(job.created.length, 1); assert.deepEqual(job.failures, [{ serverId: "2", error: "RouterOS unavailable" }]); break }
await new Promise(resolve => setImmediate(resolve)); assert.ok(attempt < 19)
}
assert.equal(created, 2); assert.ok(eventCalls >= 2)
const requestId = "11111111-1111-4111-8111-111111111111", before = created
const repeated = await Promise.all([post({ requestId, serverIds: ["1"], notes: " manual " }), post({ requestId, serverIds: [1], notes: "manual" })])
assert.ok(repeated.every(response => response.statusCode === 202 && response.json().jobId === requestId))
await new Promise(resolve => setImmediate(resolve)); assert.equal(created, before + 1)
assert.equal((await post({ requestId, serverIds: ["1"], notes: "manual" })).statusCode, 202); assert.equal(created, before + 1)
assert.equal((await post({ requestId, serverIds: ["2"], notes: "manual" })).statusCode, 409)
assert.equal((await post({ requestId, serverIds: ["1"], notes: "other" })).statusCode, 409)
const success = await app.inject({ method: "POST", url: "/api/backups/backup-1/restore" }); assert.equal(success.statusCode, 200); assert.deepEqual(success.json(), { filename: "config.rsc", serverName: "router" }); assert.equal(restored, 1)
restoreFail = true; const failure = await app.inject({ method: "POST", url: "/api/backups/backup-1/restore" }); assert.equal(failure.statusCode, 400); assert.equal(failure.json().error, "import failed")
assert.equal(permissionForRequest("POST", "/api/backups/create"), "mm:backups:write"); assert.equal(permissionForRequest("POST", "/api/backups/backup-1/restore"), "mm:backups:write"); assert.equal(permissionForRequest("GET", "/api/backups/jobs/job"), "mm:backups:read")
await app.close(); console.log("Backups HTTP: event failure does not relabel created/restore, invalid targets have no side effects, partial results and RBAC")