Files
proxdeploy/tests/vorlagen.test.ts
florian.wildeandClaude Opus 5.5 1b1ee4b876 feat(vorlagen): Vorlagen (Presets) für Assistent, API und CLI
Gespeicherte Kombinationen aus Distribution, Ressourcen und Profilen.
Im Assistenten mit einem Klick vorbelegt oder dort gespeichert; die API
nimmt „vorlage“ entgegen (Grundlage für --preset der CLI). Eine Vorlage
liegt unter der Anfrage – was die Anfrage selbst sagt, gewinnt.

Beim ersten Start wird das Beispiel „Docker-Host klein“ aus der Vorgabe
angelegt, einmalig.

Closes #012

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:44:33 +02:00

131 lines
4.6 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import * as liste from "@/app/api/v1/vorlagen/route";
import * as einzeln from "@/app/api/v1/vorlagen/[name]/route";
import { verschluesseln } from "@/lib/geheimnisse";
import { vorlageAnwenden } from "@/lib/schemas/vorlage";
import { ausrollenVorbereiten } from "@/server/services/ausrollen";
import { katalogSynchronisieren } from "@/server/services/katalog";
import { beispielVorlageAnlegen } from "@/server/services/vorlagen";
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
import { angemeldet, anfrage, kontext } from "./helpers/anfragen";
const SCHLUESSEL =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFTdkCuqQ6mF7I8QzmkNaQpKj7S/I2+yuxWeYasJjLd florian@test";
describe("Vorlage anwenden", () => {
it("Anfrage gewinnt; netz und variablen werden feldweise gemischt, Listen ersetzt", () => {
const ergebnis = vorlageAnwenden(
{
cores: 2,
speicherMb: 4096,
profile: ["base", "docker"],
netz: { bridge: "vmbr1", vlan: 20 },
variablen: { "zabbix-agent": { server: "zbx" } },
},
{
cores: 4,
netz: { modus: "dhcp" },
profile: ["base"],
variablen: { "zabbix-agent": { server_active: "zbx2" } },
name: undefined,
},
);
expect(ergebnis).toEqual({
cores: 4,
speicherMb: 4096,
profile: ["base"],
netz: { bridge: "vmbr1", vlan: 20, modus: "dhcp" },
variablen: { "zabbix-agent": { server: "zbx", server_active: "zbx2" } },
});
});
});
describe.runIf(datenbankVerfuegbar)("Vorlagen", () => {
beforeEach(async () => {
await datenbankLeeren();
await katalogSynchronisieren();
});
it("Beispiel „Docker-Host klein“ wird einmal angelegt und kommt nach dem Löschen nicht wieder", async () => {
await beispielVorlageAnlegen();
expect(
(await testPrisma.vorlage.findUniqueOrThrow({ where: { name: "docker-host-klein" } })).parameter,
).toMatchObject({
distribution: "debian-13",
cores: 2,
speicherMb: 4096,
profile: ["base", "docker"],
});
await testPrisma.vorlage.delete({ where: { name: "docker-host-klein" } });
await beispielVorlageAnlegen();
expect(await testPrisma.vorlage.count()).toBe(0);
});
it("CRUD über die API; Operator darf pflegen; Name, IP und Schlüssel gehören nicht hinein", async () => {
const operator = (await angemeldet("OPERATOR")).cookie;
const neu = {
name: "Web-Klein",
titel: "Web klein",
parameter: { typ: "vm", distribution: "debian-13", cores: 1 },
};
const angelegt = await liste.POST(anfrage("POST", "/api/v1/vorlagen", operator, neu));
expect(angelegt.status).toBe(201);
expect((await angelegt.json()).name).toBe("web-klein");
expect((await liste.POST(anfrage("POST", "/api/v1/vorlagen", operator, neu))).status).toBe(409);
const mitName = await liste.POST(
anfrage("POST", "/api/v1/vorlagen", operator, { ...neu, name: "x", parameter: { name: "web01" } }),
);
expect(mitName.status).toBe(400);
const geaendert = await einzeln.PUT(
anfrage("PUT", "/api/v1/vorlagen/web-klein", operator, {
...neu,
titel: "Web, klein",
parameter: { cores: 2 },
}),
kontext({ name: "web-klein" }),
);
expect((await geaendert.json()).parameter).toEqual({ cores: 2 });
expect(
(
await einzeln.DELETE(
anfrage("DELETE", "/api/v1/vorlagen/web-klein", operator),
kontext({ name: "web-klein" }),
)
).status,
).toBe(204);
expect(await testPrisma.protokoll.count({ where: { ziel: "vorlage:web-klein" } })).toBe(3);
});
it("Ausrollen mit vorlage: Werte der Vorlage, Anfrage gewinnt, Name im CLI-Befehl", async () => {
await beispielVorlageAnlegen();
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 v = await ausrollenVorbereiten({
vorlage: "docker-host-klein",
server: "rz1",
name: "web01",
storage: "local-zfs",
speicherMb: 8192,
netz: { bridge: "vmbr0" },
zugang: { benutzer: "florian", sshSchluessel: [SCHLUESSEL] },
});
expect(v.anfrage).toMatchObject({
typ: "vm",
distribution: "debian-13",
cores: 2,
speicherMb: 8192,
diskGb: 40,
vorlage: "docker-host-klein",
});
expect(v.profile.map((p) => p.profil.id)).toEqual(["base", "docker"]);
});
});