import { afterEach, describe, expect, it } from "vitest"; import { phasenAusfuehren, type EreignisSenke } from "@/engine/ablauf/ablauf"; import { aufraeumSchritte } from "@/engine/ablaeufe/aufraeumen"; import { ProxmoxClient } from "@/engine/proxmox/client"; import { MOCK_TOKEN_GEHEIMNIS, MOCK_TOKEN_ID, ProxmoxMock } from "../proxmox-mock/mock"; import { szenarioEinrichten } from "../proxmox-mock/szenario"; const UPID = (art: string) => `UPID:pve02:00001234:00ABCDEF:6710F00D:${art}:130:proxdeploy@pve!test:`; const aufraeumen: Array<() => Promise> = []; afterEach(async () => { while (aufraeumen.length) await aufraeumen.pop()!(); }); type Gast = { type: "qemu" | "lxc"; node: string; status: string; template?: number; config: Record; }; async function aufbauen(gast: Gast | null) { const mock = await new ProxmoxMock().starten(); szenarioEinrichten(mock, { version: "9.2.1" }); mock .route("GET", "/cluster/resources", () => ({ data: [ { vmid: 100, type: "qemu", node: "pve01", status: "running", name: "anderer" }, ...(gast ? [ { vmid: 120, type: gast.type, node: gast.node, status: gast.status, name: "web01", template: gast.template ?? 0, }, ] : []), ], })) .route("GET", /^\/nodes\/pve02\/(qemu|lxc)\/120\/config$/, () => ({ data: gast?.config ?? {} })) .route("POST", /^\/nodes\/pve02\/(qemu|lxc)\/120\/status\/stop$/, () => ({ data: UPID("qmstop") })) .route("DELETE", /^\/nodes\/pve02\/(qemu|lxc)\/120$/, () => ({ data: UPID("qmdestroy") })) .route("GET", /\/tasks\/[^/]+\/status$/, () => ({ data: { status: "stopped", exitstatus: "OK" } })); const client = new ProxmoxClient({ url: mock.url, tokenId: MOCK_TOKEN_ID, tokenGeheimnis: MOCK_TOKEN_GEHEIMNIS, tlsPruefen: false, fingerabdruck: mock.fingerabdruck, }); aufraeumen.push(async () => { await client.schliessen(); await mock.stoppen(); }); const phasen: Record = {}; const senke: EreignisSenke = { phasenAnlegen: async () => {}, phaseGestartet: async () => {}, phaseDetail: async () => {}, phaseBeendet: async (s, status, a) => { phasen[s] = { status, detail: a.detail, ursache: a.fehler?.ursache }; }, log: async () => {}, }; const ausfuehren = (typ: "VM" | "LXC" = "VM") => phasenAusfuehren( aufraeumSchritte({ client, typ, vmid: 120, knoten: "pve01", auftragKurzId: "d-abcde" }), senke, ); const schreibend = () => mock.anfragen.filter((a) => a.methode !== "GET").map((a) => `${a.methode} ${a.pfad}`); return { mock, ausfuehren, phasen, schreibend }; } const EIGEN = "Ausgerollt mit ProxDeploy (Auftrag d-abcde)\nDistribution: Debian 13"; describe("Aufräumen", () => { it("stoppt und löscht den eigenen Gast – auch wenn er inzwischen auf einem anderen Node liegt", async () => { const t = await aufbauen({ type: "qemu", node: "pve02", status: "running", config: { name: "web01", description: EIGEN }, }); expect((await t.ausfuehren()).ok).toBe(true); expect(t.schreibend()).toEqual([ "POST /nodes/pve02/qemu/120/status/stop", "DELETE /nodes/pve02/qemu/120", ]); const loeschen = t.mock.anfragen.find((a) => a.methode === "DELETE")!; expect(loeschen.abfrage.get("destroy-unreferenced-disks")).toBe("1"); expect(t.phasen["loeschen"]!.detail).toBe("VM 120 mit Disks gelöscht"); }); it("Container, der nicht läuft: Stoppen übersprungen", async () => { const t = await aufbauen({ type: "lxc", node: "pve02", status: "stopped", config: { hostname: "ct01", description: EIGEN }, }); expect((await t.ausfuehren("LXC")).ok).toBe(true); expect(t.phasen["stoppen"]!.status).toBe("UEBERSPRUNGEN"); expect(t.schreibend()).toEqual(["DELETE /nodes/pve02/lxc/120"]); }); it("schon weg: Erfolg ohne Löschen", async () => { const t = await aufbauen(null); expect((await t.ausfuehren()).ok).toBe(true); expect(t.phasen["pruefungen"]!.detail).toMatch(/gibt es nicht mehr/); expect(t.schreibend()).toEqual([]); }); it.each([ [ "fremde Beschreibung", { type: "qemu", node: "pve02", status: "running", config: { description: "Produktiv-DB" } }, /gehört nicht erkennbar/, ], [ "anderer Auftrag", { type: "qemu", node: "pve02", status: "running", config: { description: "Ausgerollt mit ProxDeploy (Auftrag d-zzzzz)" }, }, /gehört nicht erkennbar/, ], [ "Template", { type: "qemu", node: "pve02", status: "stopped", template: 1, config: { description: EIGEN, template: 1 }, }, /ist ein Template/, ], [ "gesperrt", { type: "qemu", node: "pve02", status: "stopped", config: { description: EIGEN, lock: "clone" } }, /gesperrt \(clone\)/, ], [ "falscher Typ", { type: "lxc", node: "pve02", status: "stopped", config: { description: EIGEN } }, /kein VM/, ], ] as Array<[string, Gast, RegExp]>)("%s: bricht ab und fasst nichts an", async (_name, gast, meldung) => { const t = await aufbauen(gast); const ergebnis = await t.ausfuehren("VM"); expect(ergebnis.ok).toBe(false); expect(t.phasen["pruefungen"]!.ursache).toMatch(meldung); expect(t.schreibend()).toEqual([]); }); });