import { describe, expect, it } from "vitest"; import { benoetigteRechte, faehigkeitenAbleiten, rechtePruefen, versionLesen, type StorageInfo, } from "@/engine/proxmox/faehigkeiten"; const v = (text: string) => versionLesen(text)!; const storage = (name: string, inhalte: string[]): StorageInfo => ({ storage: name, typ: "dir", inhalte, aktiv: true, geteilt: false, frei: null, gesamt: null, }); describe("PVE-Version", () => { it("liest übliche Schreibweisen", () => { expect(v("9.2.1")).toMatchObject({ major: 9, minor: 2, patch: 1 }); expect(v("8.4")).toMatchObject({ major: 8, minor: 4, patch: 0 }); expect(versionLesen("kaputt")).toBeNull(); }); }); describe("Rolle ProxDeploy je Version", () => { const namen = (text: string) => benoetigteRechte(v(text)) .filter((r) => r.pflicht) .map((r) => r.recht); it("PVE 9 nutzt die Guest-Agent-Rechte statt VM.Monitor", () => { expect(namen("9.2.1")).toEqual( expect.arrayContaining(["VM.GuestAgent.Audit", "VM.GuestAgent.Unrestricted"]), ); expect(namen("9.2.1")).not.toContain("VM.Monitor"); }); it("PVE 8 braucht VM.Monitor für den Guest-Agent", () => { expect(namen("8.4.1")).toContain("VM.Monitor"); expect(namen("8.4.1")).not.toContain("VM.GuestAgent.Unrestricted"); }); it("download-url: Sys.AccessNetwork ab 8.2, davor Sys.Modify", () => { expect(namen("8.2.0")).toContain("Sys.AccessNetwork"); expect(namen("8.1.4")).toContain("Sys.Modify"); expect(namen("8.1.4")).not.toContain("Sys.AccessNetwork"); }); }); describe("Rechteprüfung", () => { const version = v("9.2.1"); const voll = Object.fromEntries(benoetigteRechte(version).map((r) => [r.recht, 1])); it("meldet nichts, wenn alles da ist", () => { const rechte = { "/": voll, "/vms": voll, "/sdn/zones/localnetwork": voll, "/nodes/pve01": voll, "/storage/local-zfs": voll, }; expect(rechtePruefen(version, rechte, { storages: ["local-zfs"], nodes: ["pve01"] })).toEqual([]); }); it("nennt fehlende Rechte mit Pfad und Zweck", () => { const ohneAgent = { ...voll, "VM.GuestAgent.Unrestricted": 0 }; const rechte = { "/": voll, "/vms": ohneAgent, "/sdn/zones/localnetwork": voll, "/nodes/pve01": voll, "/storage/local-zfs": voll, }; expect(rechtePruefen(version, rechte, { storages: ["local-zfs"], nodes: ["pve01"] })).toEqual([ expect.objectContaining({ recht: "VM.GuestAgent.Unrestricted", pfad: "/vms", pflicht: true }), ]); }); it("Storage-Rechte: einer reicht; Node-Rechte: jeder Node braucht sie", () => { const rechte = { "/": voll, "/vms": voll, "/sdn/zones/localnetwork": voll, "/nodes/pve01": voll, "/nodes/pve02": {}, "/storage/a": voll, "/storage/b": {}, }; const fehlend = rechtePruefen(version, rechte, { storages: ["a", "b"], nodes: ["pve01", "pve02"] }); expect(fehlend.filter((f) => f.pflicht).map((f) => `${f.recht} ${f.pfad}`)).toEqual([ "Sys.AccessNetwork /nodes/pve02", ]); }); }); describe("Strategien", () => { const mitImport = [storage("local", ["iso", "import", "snippets"])]; const ohneImport = [storage("local", ["iso", "snippets"])]; it("PVE 9.2 mit import-Storage ohne SSH: Image ginge per API, der Guest-Agent aber nicht – außer mit Bau-Adresse", () => { const ohne = faehigkeitenAbleiten({ version: v("9.2.1"), storages: mitImport, ssh: "aus", rechteFehlen: false, }); expect(ohne.templateBau.weg).toBe("nicht_moeglich"); expect(ohne.templateBau.grund).toMatch(/Bau-Adresse/); const mit = faehigkeitenAbleiten({ version: v("9.2.1"), storages: mitImport, ssh: "aus", rechteFehlen: false, bauNetz: true, }); expect(mit.templateBau).toMatchObject({ weg: "api" }); expect(mit.templateBau.grund).toMatch(/Bau-Adresse/); expect(mit.vmProvisionierung.weg).toBe("api"); expect(mit.lxcProvisionierung).toMatchObject({ weg: "ssh", ueber: "gast" }); expect(mit.snippets.weg).toBe("nicht_moeglich"); }); it("PVE 8.3: Template-Bau nur per SSH; ohne SSH mit Begründung unmöglich", () => { expect( faehigkeitenAbleiten({ version: v("8.3.5"), storages: mitImport, ssh: "ok", rechteFehlen: false }) .templateBau.weg, ).toBe("ssh"); const ohne = faehigkeitenAbleiten({ version: v("8.3.5"), storages: mitImport, ssh: "aus", rechteFehlen: false, }); expect(ohne.templateBau).toMatchObject({ weg: "nicht_moeglich" }); expect(ohne.templateBau.grund).toContain("8.4"); }); it("PVE 8.4 ohne import-Storage nennt den Ausweg", () => { const f = faehigkeitenAbleiten({ version: v("8.4.1"), storages: ohneImport, ssh: "aus", rechteFehlen: false, }); expect(f.templateBau.grund).toMatch(/Inhaltstyp „Import“/); }); it("mit SSH: Snippets, pct exec und erweiterte LXC-Features", () => { const f = faehigkeitenAbleiten({ version: v("9.2.1"), storages: mitImport, ssh: "ok", rechteFehlen: false, }); expect(f.snippets.weg).toBe("ssh"); expect(f.lxcProvisionierung).toMatchObject({ ueber: "node" }); expect(f.lxcFeaturesErweitert.weg).toBe("ssh"); }); it("unbestätigter Host-Key zählt nicht als SSH", () => { const f = faehigkeitenAbleiten({ version: v("8.3.5"), storages: mitImport, ssh: "nicht_bestaetigt", rechteFehlen: false, }); expect(f.templateBau.weg).toBe("nicht_moeglich"); }); });