Files
proxdeploy/tests/ausrollen-api.test.ts
florian.wildeandClaude Opus 5.5 9310164bc2 feat(ausrollen): VM ausrollen mit Assistent, Dry-Run und CLI-Befehl
Eine VM aus dem ProxDeploy-Template ausrollen, vollständig per API:
Klonen, Hardware, cloud-init, Start, Guest-Agent und Provisionierung der
Profile über den Agent – ohne SSH. Der Fortschritt kommt aus der
Logdatei im Gast, je Profil eine Marke.

Der Dry-Run läuft durch dieselben Phasen mit einem Trocken-Client und
zeigt jeden geplanten Proxmox-Aufruf und den passenden CLI-Befehl. Der
Assistent hat 9 Schritte, den Zustand in der URL und bietet an, ein
fehlendes Template im selben Auftrag zu bauen.

Auf pve-demo abgenommen: pd-test01 (base, docker) per API, pd-test02
(base, zabbix-agent) über den Assistenten, beide per SSH geprüft. Dabei
gefunden: file-write scheitert an Umlauten – Skripte gehen jetzt
base64-kodiert hinüber.

Closes #008

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:34:15 +02:00

90 lines
3.4 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import { POST as ausrollenPost } from "@/app/api/v1/auftraege/route";
import { verschluesseln } from "@/lib/geheimnisse";
import { cliBefehl, AusrollAnfrage } from "@/lib/schemas/ausrollen";
import { katalogSynchronisieren } from "@/server/services/katalog";
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
import { angemeldet, anfrage } from "./helpers/anfragen";
const SCHLUESSEL =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFTdkCuqQ6mF7I8QzmkNaQpKj7S/I2+yuxWeYasJjLd florian@test";
const gueltig = {
typ: "vm",
server: "rz1",
distribution: "debian-13",
name: "web01",
storage: "local-zfs",
netz: { bridge: "vmbr0" },
zugang: { benutzer: "florian", sshSchluessel: [SCHLUESSEL] },
profile: ["docker"],
};
describe("CLI-Befehl aus der Anfrage", () => {
it("nennt nur Abweichungen vom Standard und entspricht der Vorgabe", () => {
const a = AusrollAnfrage.parse({
...gueltig,
profile: ["base", "docker"],
netz: { bridge: "vmbr0", vlan: 20, modus: "statisch", ip: "10.0.20.15/24", gateway: "10.0.20.1" },
cores: 2,
speicherMb: 4096,
diskGb: 40,
});
expect(cliBefehl(a)).toBe(
"proxdeploy vm web01 --server rz1 --distro debian-13 --profile base,docker --ip 10.0.20.15/24 --gw 10.0.20.1 --vlan 20 --memory 4096 --disk 40G --storage local-zfs --user florian",
);
});
it("quotet Variablenwerte mit Leerzeichen", () => {
const a = AusrollAnfrage.parse({ ...gueltig, variablen: { "zabbix-agent": { server: "a b" } } });
expect(cliBefehl(a)).toContain("--var zabbix-agent.server='a b'");
});
});
describe.runIf(datenbankVerfuegbar)("POST /api/v1/auftraege", () => {
let cookie: string;
beforeEach(async () => {
await datenbankLeeren();
await katalogSynchronisieren();
cookie = (await angemeldet("OPERATOR")).cookie;
await testPrisma.server.create({
data: {
name: "rz1",
titel: "RZ 1",
url: "https://127.0.0.1:8006",
tokenId: "a@pve!b",
tokenGeheimnis: verschluesseln("x"),
fingerabdruck: "AA",
},
});
});
const senden = async (rumpf: unknown) => {
const antwort = await ausrollenPost(anfrage("POST", "/api/v1/auftraege", cookie, rumpf));
return { status: antwort.status, inhalt: await antwort.json() };
};
it("meldet Feldfehler einzeln: Name, Schlüssel, root", async () => {
const { status, inhalt } = await senden({
...gueltig,
name: "Web_01",
zugang: { benutzer: "root", sshSchluessel: ["kein schlüssel"] },
});
expect(status).toBe(400);
expect(Object.keys(inhalt.fehler.felder)).toEqual(
expect.arrayContaining(["name", "zugang.benutzer", "zugang.sshSchluessel.0"]),
);
});
it("fehlende Profil-Variable landet am richtigen Feld", async () => {
const { status, inhalt } = await senden({ ...gueltig, profile: ["zabbix-agent"] });
expect(status).toBe(400);
expect(inhalt.fehler.felder).toEqual({ "variablen.zabbix-agent.server": "Zabbix-Server fehlt." });
});
it("deaktivierte Distribution und unbekannter Server werden abgewiesen", async () => {
await testPrisma.distribution.update({ where: { id: "debian-13" }, data: { aktiv: false } });
expect((await senden(gueltig)).status).toBe(400);
expect((await senden({ ...gueltig, server: "gibtsnicht" })).status).toBe(404);
});
});