Files
proxdeploy/tests/engine/faehigkeiten.test.ts
T
florian.wildeandClaude Opus 5.5 21edff2141 feat(templates): Template-Bau ohne SSH über eine Bau-Adresse
Ohne SSH zum Node kommt der Guest-Agent jetzt über eine feste
Bau-Adresse ins Template: cloud-init setzt Adresse und den
Bereitstellungsschlüssel für den Standardbenutzer, ProxDeploy meldet
sich per SSH in der VM an, installiert den Agent und entfernt danach
Schlüssel, Adresse und cloud-init-Angaben.

Dabei korrigiert: der Verbindungstest meldete „Template-Bau per API“
auch ohne Weg, den Agent einzubauen. Im Protokoll erscheinen
unveränderte JSON-Felder nicht mehr als geändert.

Refs #021

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:36:38 +02:00

174 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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");
});
});